├── .nvmrc ├── .ruby-version ├── .rbenv-gemsets ├── .standard.yml ├── .rufo ├── spec ├── fixtures │ ├── hook │ │ ├── spec │ │ │ ├── user_spec.rb │ │ │ └── api_spec.rb │ │ ├── app │ │ │ ├── models │ │ │ │ ├── api_key.rb │ │ │ │ ├── show.rb │ │ │ │ ├── user.rb │ │ │ │ └── configuration.rb │ │ │ └── controllers │ │ │ │ ├── api │ │ │ │ └── api_keys_controller.rb │ │ │ │ └── organizations_controller.rb │ │ ├── .gitignore │ │ ├── attr_accessor.rb │ │ ├── pkg_a │ │ │ └── a.rb │ │ ├── labels.rb │ │ ├── sub_packages.rb │ │ ├── constructor.rb │ │ ├── compare.rb │ │ ├── report_parameters.rb │ │ ├── job.rb │ │ ├── kwargs.rb │ │ ├── custom_instance_method.rb │ │ ├── method_named_call.rb │ │ ├── prepended_override.rb │ │ ├── exclude.rb │ │ ├── proc_no_autosplat.rb │ │ ├── args_to_kwargs.rb │ │ ├── protected_method.rb │ │ ├── instance_method.rb │ │ ├── anonkwargs_passed.rb │ │ ├── anonargs_passed.rb │ │ └── exception_method.rb │ ├── rails5_users_app │ │ ├── log │ │ │ └── .keep │ │ ├── lib │ │ │ └── tasks │ │ │ │ └── .keep │ │ ├── .ruby-version │ │ ├── app │ │ │ ├── models │ │ │ │ ├── concerns │ │ │ │ │ └── .keep │ │ │ │ ├── activerecord │ │ │ │ │ └── user.rb │ │ │ │ └── sequel │ │ │ │ │ └── user.rb │ │ │ ├── controllers │ │ │ │ ├── concerns │ │ │ │ │ └── .keep │ │ │ │ ├── application_controller.rb │ │ │ │ ├── health_controller.rb │ │ │ │ ├── users_controller.rb │ │ │ │ └── api │ │ │ │ │ └── users_controller.rb │ │ │ └── views │ │ │ │ ├── users │ │ │ │ └── index.html.haml │ │ │ │ └── layouts │ │ │ │ └── application.html.haml │ │ ├── .dockerignore │ │ ├── .rspec │ │ ├── config │ │ │ ├── database.yml │ │ │ ├── boot.rb │ │ │ ├── environment.rb │ │ │ ├── initializers │ │ │ │ ├── mime_types.rb │ │ │ │ ├── filter_parameter_logging.rb │ │ │ │ ├── application_controller_renderer.rb │ │ │ │ ├── wrap_parameters.rb │ │ │ │ ├── backtrace_silencers.rb │ │ │ │ ├── cors.rb │ │ │ │ └── inflections.rb │ │ │ ├── routes.rb │ │ │ ├── credentials.yml.enc │ │ │ └── locales │ │ │ │ └── en.yml │ │ ├── features │ │ │ ├── support │ │ │ │ ├── env.rb │ │ │ │ ├── hooks.rb │ │ │ │ └── steps.rb │ │ │ └── api_users.feature │ │ ├── public │ │ │ └── robots.txt │ │ ├── config.ru │ │ ├── bin │ │ │ ├── rails │ │ │ ├── setup │ │ │ ├── update │ │ │ ├── gli │ │ │ ├── rake │ │ │ ├── thor │ │ │ ├── rackup │ │ │ ├── appmap │ │ │ ├── byebug │ │ │ ├── ldiff │ │ │ ├── rspec │ │ │ ├── sequel │ │ │ ├── htmldiff │ │ │ ├── nokogiri │ │ │ ├── ruby-parse │ │ │ ├── sprockets │ │ │ └── ruby-rewrite │ │ ├── Rakefile │ │ ├── db │ │ │ ├── migrate │ │ │ │ └── 20190728211408_create_users.rb │ │ │ └── schema.rb │ │ ├── appmap.yml │ │ ├── users_app │ │ │ └── .gitignore │ │ ├── spec │ │ │ ├── controllers │ │ │ │ └── users_controller_spec.rb │ │ │ └── models │ │ │ │ └── user_spec.rb │ │ ├── Gemfile │ │ └── .gitignore │ ├── rails6_users_app │ │ ├── log │ │ │ └── .keep │ │ ├── lib │ │ │ └── tasks │ │ │ │ └── .keep │ │ ├── app │ │ │ ├── models │ │ │ │ ├── concerns │ │ │ │ │ └── .keep │ │ │ │ ├── activerecord │ │ │ │ │ └── user.rb │ │ │ │ └── sequel │ │ │ │ │ └── user.rb │ │ │ ├── controllers │ │ │ │ ├── concerns │ │ │ │ │ └── .keep │ │ │ │ ├── application_controller.rb │ │ │ │ ├── health_controller.rb │ │ │ │ ├── users_controller.rb │ │ │ │ └── api │ │ │ │ │ └── users_controller.rb │ │ │ └── views │ │ │ │ ├── users │ │ │ │ └── index.html.haml │ │ │ │ └── layouts │ │ │ │ └── application.html.haml │ │ ├── .dockerignore │ │ ├── .rspec │ │ ├── config │ │ │ ├── database.yml │ │ │ ├── boot.rb │ │ │ ├── environment.rb │ │ │ ├── initializers │ │ │ │ ├── mime_types.rb │ │ │ │ ├── filter_parameter_logging.rb │ │ │ │ ├── application_controller_renderer.rb │ │ │ │ ├── wrap_parameters.rb │ │ │ │ ├── backtrace_silencers.rb │ │ │ │ ├── cors.rb │ │ │ │ └── inflections.rb │ │ │ ├── routes.rb │ │ │ ├── credentials.yml.enc │ │ │ └── locales │ │ │ │ └── en.yml │ │ ├── features │ │ │ ├── support │ │ │ │ ├── env.rb │ │ │ │ ├── hooks.rb │ │ │ │ └── steps.rb │ │ │ └── api_users.feature │ │ ├── public │ │ │ └── robots.txt │ │ ├── config.ru │ │ ├── bin │ │ │ ├── rails │ │ │ ├── setup │ │ │ ├── update │ │ │ ├── gli │ │ │ ├── rake │ │ │ ├── thor │ │ │ ├── rackup │ │ │ ├── appmap │ │ │ ├── byebug │ │ │ ├── ldiff │ │ │ ├── rspec │ │ │ ├── sequel │ │ │ ├── htmldiff │ │ │ ├── nokogiri │ │ │ ├── ruby-parse │ │ │ ├── sprockets │ │ │ └── ruby-rewrite │ │ ├── package.json │ │ ├── Rakefile │ │ ├── db │ │ │ ├── migrate │ │ │ │ └── 20190728211408_create_users.rb │ │ │ └── schema.rb │ │ ├── test │ │ │ ├── controllers │ │ │ │ └── functional_calc_test.rb │ │ │ └── integration │ │ │ │ └── integration_calc_test.rb │ │ ├── appmap.yml │ │ ├── users_app │ │ │ └── .gitignore │ │ ├── spec │ │ │ ├── controllers │ │ │ │ └── users_controller_spec.rb │ │ │ ├── requests │ │ │ │ └── rswag_spec.rb │ │ │ └── models │ │ │ │ └── user_spec.rb │ │ ├── .gitignore │ │ └── Gemfile │ ├── rails7_users_app │ │ ├── log │ │ │ └── .keep │ │ ├── lib │ │ │ ├── assets │ │ │ │ └── .keep │ │ │ ├── tasks │ │ │ │ ├── .keep │ │ │ │ └── count_users.rake │ │ │ └── hijacker.rb │ │ ├── storage │ │ │ └── .keep │ │ ├── public │ │ │ ├── favicon.ico │ │ │ ├── apple-touch-icon.png │ │ │ ├── apple-touch-icon-precomposed.png │ │ │ └── robots.txt │ │ ├── app │ │ │ ├── assets │ │ │ │ ├── images │ │ │ │ │ └── .keep │ │ │ │ ├── config │ │ │ │ │ └── manifest.js │ │ │ │ └── stylesheets │ │ │ │ │ └── application.css │ │ │ ├── models │ │ │ │ ├── concerns │ │ │ │ │ └── .keep │ │ │ │ ├── activerecord │ │ │ │ │ └── user.rb │ │ │ │ └── sequel │ │ │ │ │ └── user.rb │ │ │ ├── controllers │ │ │ │ ├── concerns │ │ │ │ │ └── .keep │ │ │ │ ├── application_controller.rb │ │ │ │ ├── health_controller.rb │ │ │ │ ├── users_controller.rb │ │ │ │ └── api │ │ │ │ │ └── users_controller.rb │ │ │ ├── helpers │ │ │ │ └── application_helper.rb │ │ │ ├── views │ │ │ │ ├── users │ │ │ │ │ └── index.html.haml │ │ │ │ └── layouts │ │ │ │ │ └── application.html.haml │ │ │ ├── javascript │ │ │ │ ├── application.js │ │ │ │ └── controllers │ │ │ │ │ ├── hello_controller.js │ │ │ │ │ ├── application.js │ │ │ │ │ └── index.js │ │ │ └── jobs │ │ │ │ ├── print_user_count_job.rb │ │ │ │ └── application_job.rb │ │ ├── .dockerignore │ │ ├── .rspec │ │ ├── config │ │ │ ├── database.yml │ │ │ ├── boot.rb │ │ │ ├── environment.rb │ │ │ ├── cable.yml │ │ │ ├── routes.rb │ │ │ ├── importmap.rb │ │ │ ├── initializers │ │ │ │ ├── filter_parameter_logging.rb │ │ │ │ ├── permissions_policy.rb │ │ │ │ ├── inflections.rb │ │ │ │ └── content_security_policy.rb │ │ │ ├── credentials.yml.enc │ │ │ ├── locales │ │ │ │ └── en.yml │ │ │ └── storage.yml │ │ ├── appmap.yml │ │ ├── bin │ │ │ ├── importmap │ │ │ ├── rails │ │ │ ├── rake │ │ │ ├── rspec │ │ │ └── setup │ │ ├── config.ru │ │ ├── Rakefile │ │ ├── db │ │ │ ├── migrate │ │ │ │ └── 20190728211408_create_users.rb │ │ │ └── schema.rb │ │ ├── .gitattributes │ │ ├── README.md │ │ ├── spec │ │ │ ├── system │ │ │ │ └── rack_spec.rb │ │ │ ├── requests │ │ │ │ └── users_spec.rb │ │ │ ├── controllers │ │ │ │ └── users_controller_spec.rb │ │ │ └── models │ │ │ │ └── user_spec.rb │ │ ├── .gitignore │ │ └── Gemfile │ ├── config │ │ ├── incomplete_config.yml │ │ ├── invalid_config.yml │ │ ├── invalid_yaml_config.yml │ │ ├── valid_config.yml │ │ ├── missing_path_or_gem.yml │ │ └── maximal_config.yml │ ├── rack_users_app │ │ ├── .gitignore │ │ ├── .dockerignore │ │ ├── config.ru │ │ ├── appmap.yml │ │ ├── Gemfile │ │ └── lib │ │ │ └── app.rb │ ├── depends │ │ ├── .gitignore │ │ ├── app │ │ │ ├── models │ │ │ │ ├── show.rb │ │ │ │ ├── user.rb │ │ │ │ ├── api_key.rb │ │ │ │ └── configuration.rb │ │ │ └── controllers │ │ │ │ ├── api │ │ │ │ └── api_keys_controller.rb │ │ │ │ └── organizations_controller.rb │ │ ├── spec │ │ │ ├── api_spec.rb │ │ │ ├── user_spec.rb │ │ │ └── actual_rspec_test.rb │ │ └── test │ │ │ └── actual_minitest_test.rb │ └── database.yml ├── handler │ └── class_with_eval.rb ├── rails_test_spec.rb ├── depends │ └── spec_helper.rb ├── open_spec.rb ├── service │ └── integration_test_path_finder_spec.rb ├── ruby_method_spec.rb ├── railtie_spec.rb ├── class_map_spec.rb ├── method_hash_key_spec.rb ├── hook_log_spec.rb ├── around_recording_spec.rb └── spec_helper.rb ├── examples └── mock_webapp │ ├── appmap.yml │ ├── Gemfile │ ├── lib │ └── mock_webapp │ │ ├── request.rb │ │ ├── user.rb │ │ └── controller.rb │ └── exe │ └── mock_webapp_request ├── appmap.yml ├── test ├── fixtures │ ├── gem_test │ │ ├── appmap.yml │ │ ├── Gemfile │ │ └── test │ │ │ └── parser_test.rb │ ├── cli_record_test │ │ ├── appmap.yml │ │ └── lib │ │ │ └── cli_record_test │ │ │ └── main.rb │ ├── process_recorder │ │ ├── appmap.yml │ │ └── hello.rb │ ├── rspec_recorder │ │ ├── appmap.yml │ │ ├── lib │ │ │ └── hello.rb │ │ ├── Gemfile │ │ └── spec │ │ │ ├── failed_spec.rb │ │ │ ├── plain_hello_spec.rb │ │ │ ├── labeled_hello_spec.rb │ │ │ └── decorated_hello_spec.rb │ ├── cucumber4_recorder │ │ ├── appmap.yml │ │ ├── lib │ │ │ └── hello.rb │ │ ├── features │ │ │ ├── say_hello.feature │ │ │ └── support │ │ │ │ ├── env.rb │ │ │ │ ├── steps.rb │ │ │ │ └── hooks.rb │ │ └── Gemfile │ ├── cucumber_recorder │ │ ├── appmap.yml │ │ ├── lib │ │ │ └── hello.rb │ │ ├── features │ │ │ ├── say_hello.feature │ │ │ └── support │ │ │ │ ├── env.rb │ │ │ │ ├── steps.rb │ │ │ │ └── hooks.rb │ │ └── Gemfile │ ├── minitest_recorder │ │ ├── appmap.yml │ │ ├── lib │ │ │ └── hello.rb │ │ ├── test │ │ │ ├── test_helper.rb │ │ │ ├── hello_test.rb │ │ │ ├── hello_failed_test.rb │ │ │ └── hello_tagged_test.rb │ │ └── Gemfile │ ├── mocha_mock_app │ │ ├── lib │ │ │ └── sheep.rb │ │ ├── appmap.yml │ │ ├── Gemfile │ │ └── test │ │ │ └── sheep_test.rb │ ├── openssl_recorder │ │ ├── appmap.yml │ │ ├── Gemfile │ │ └── lib │ │ │ ├── openssl_key_sign.rb │ │ │ └── openssl_encrypt.rb │ └── bundle_vendor_app │ │ ├── appmap.yml │ │ ├── Gemfile │ │ └── cli.rb ├── util_test.rb ├── inspect_cli_test.rb ├── test_helper.rb ├── agent_setup_init_test.rb ├── record_process_test.rb ├── bundle_vendor_test.rb ├── gem_test.rb └── expectations │ ├── openssl_test_key_sign2-3.1.json │ ├── openssl_test_key_sign2.json │ └── openssl_test_key_sign1.json ├── lib └── appmap │ ├── depends.rb │ ├── gem_hooks │ ├── activejob-cancel.yml │ ├── pandoc-ruby.yml │ ├── rails.yml │ ├── sprockets.yml │ ├── railties.yml │ ├── resque.yml │ ├── sidekiq.yml │ ├── activejob.yml │ ├── jwt.yml │ ├── cancancan.yml │ ├── random.yml │ ├── devise.yml │ └── actionview.yml │ ├── swagger.rb │ ├── builtin_hooks │ ├── logger.yml │ ├── net │ │ └── http.yml │ ├── json.yml │ ├── open3.yml │ ├── psych.yml │ ├── ruby.yml │ └── activesupport.yml │ ├── recording_methods.rb │ ├── command_error.rb │ ├── version.rb │ ├── handler │ ├── marshal_load_handler.rb │ ├── open_ssl_handler.rb │ ├── function_handler.rb │ └── rails │ │ ├── test_route.rb │ │ ├── render_handler.rb │ │ └── context.rb │ ├── service │ ├── test_framework_detector.rb │ ├── guesser.rb │ └── config_analyzer.rb │ ├── command │ ├── index.rb │ ├── inspect.rb │ └── agent_setup │ │ ├── config.rb │ │ ├── init.rb │ │ ├── validate.rb │ │ └── status.rb │ ├── record.rb │ ├── depends │ └── util.rb │ ├── swagger │ ├── stable.rb │ └── markdown_descriptions.rb │ ├── handler.rb │ └── metadata.rb ├── .dockerignore ├── Dockerfile.pg ├── .rubocop.yml ├── Gemfile ├── exe ├── appmap-index ├── appmap-inspect ├── appmap-agent-init ├── appmap-agent-status ├── appmap-agent-validate └── appmap-agent-config ├── docker-compose.yml ├── .gitignore ├── .releaserc.yml ├── release.sh ├── ext └── appmap │ └── extconf.rb ├── package.json ├── README_CI.md ├── .travis.yml ├── Rakefile └── CONTRIBUTING.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 14 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.0.2 2 | -------------------------------------------------------------------------------- /.rbenv-gemsets: -------------------------------------------------------------------------------- 1 | appmap-ruby 2 | -------------------------------------------------------------------------------- /.standard.yml: -------------------------------------------------------------------------------- 1 | ruby_version: 2.6 2 | -------------------------------------------------------------------------------- /.rufo: -------------------------------------------------------------------------------- 1 | # .rufo 2 | quote_style :single 3 | -------------------------------------------------------------------------------- /spec/fixtures/hook/spec/user_spec.rb: -------------------------------------------------------------------------------- 1 | # dummy -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/hook/app/models/api_key.rb: -------------------------------------------------------------------------------- 1 | # dummy -------------------------------------------------------------------------------- /spec/fixtures/hook/app/models/show.rb: -------------------------------------------------------------------------------- 1 | # dummy -------------------------------------------------------------------------------- /spec/fixtures/hook/app/models/user.rb: -------------------------------------------------------------------------------- 1 | # dummy -------------------------------------------------------------------------------- /spec/fixtures/hook/spec/api_spec.rb: -------------------------------------------------------------------------------- 1 | # dummy 2 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/config/incomplete_config.yml: -------------------------------------------------------------------------------- 1 | name: app -------------------------------------------------------------------------------- /spec/fixtures/hook/app/models/configuration.rb: -------------------------------------------------------------------------------- 1 | # dummy -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.2 2 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/public/apple-touch-icon.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/mock_webapp/appmap.yml: -------------------------------------------------------------------------------- 1 | packages: 2 | - path: lib 3 | -------------------------------------------------------------------------------- /spec/fixtures/rack_users_app/.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | 3 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/.dockerignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/.dockerignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/.dockerignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/hook/app/controllers/api/api_keys_controller.rb: -------------------------------------------------------------------------------- 1 | # dummy -------------------------------------------------------------------------------- /spec/fixtures/rack_users_app/.dockerignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | 3 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/config/database.yml: -------------------------------------------------------------------------------- 1 | ../../database.yml -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/config/database.yml: -------------------------------------------------------------------------------- 1 | ../../database.yml -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/config/database.yml: -------------------------------------------------------------------------------- 1 | ../../database.yml -------------------------------------------------------------------------------- /spec/fixtures/hook/.gitignore: -------------------------------------------------------------------------------- 1 | user_page_scenario 2 | revoke_api_key 3 | -------------------------------------------------------------------------------- /spec/fixtures/hook/app/controllers/organizations_controller.rb: -------------------------------------------------------------------------------- 1 | # dummy -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /appmap.yml: -------------------------------------------------------------------------------- 1 | name: AppMap Rubygem 2 | packages: [] 3 | appmap_dir: dummy 4 | -------------------------------------------------------------------------------- /spec/fixtures/config/invalid_config.yml: -------------------------------------------------------------------------------- 1 | name: name 2 | packages: [1,2,3] 3 | -------------------------------------------------------------------------------- /spec/fixtures/depends/.gitignore: -------------------------------------------------------------------------------- 1 | user_page_scenario 2 | revoke_api_key 3 | -------------------------------------------------------------------------------- /spec/fixtures/rack_users_app/config.ru: -------------------------------------------------------------------------------- 1 | require './lib/app.rb' 2 | run App 3 | -------------------------------------------------------------------------------- /spec/fixtures/config/invalid_yaml_config.yml: -------------------------------------------------------------------------------- 1 | name: 2 | - 3 | packages: "[]" asd 4 | -------------------------------------------------------------------------------- /test/fixtures/gem_test/appmap.yml: -------------------------------------------------------------------------------- 1 | name: gem_test 2 | packages: 3 | - gem: parser 4 | -------------------------------------------------------------------------------- /spec/fixtures/config/valid_config.yml: -------------------------------------------------------------------------------- 1 | name: appmap 2 | packages: 3 | - path: app/models 4 | -------------------------------------------------------------------------------- /spec/fixtures/rack_users_app/appmap.yml: -------------------------------------------------------------------------------- 1 | name: rack_users_app 2 | packages: 3 | - path: lib 4 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/appmap.yml: -------------------------------------------------------------------------------- 1 | name: rails7_users_app 2 | packages: 3 | - path: app 4 | -------------------------------------------------------------------------------- /test/fixtures/cli_record_test/appmap.yml: -------------------------------------------------------------------------------- 1 | name: cli_record_test 2 | packages: 3 | - path: lib 4 | -------------------------------------------------------------------------------- /test/fixtures/process_recorder/appmap.yml: -------------------------------------------------------------------------------- 1 | name: process_recorder 2 | packages: 3 | - path: . 4 | -------------------------------------------------------------------------------- /test/fixtures/rspec_recorder/appmap.yml: -------------------------------------------------------------------------------- 1 | name: rspec_recorder 2 | packages: 3 | - path: lib 4 | -------------------------------------------------------------------------------- /spec/fixtures/config/missing_path_or_gem.yml: -------------------------------------------------------------------------------- 1 | name: name 2 | packages: 3 | - dogs: are friendly 4 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/fixtures/cucumber4_recorder/appmap.yml: -------------------------------------------------------------------------------- 1 | name: cucumber_recorder 2 | packages: 3 | - path: lib 4 | -------------------------------------------------------------------------------- /test/fixtures/cucumber_recorder/appmap.yml: -------------------------------------------------------------------------------- 1 | name: cucumber_recorder 2 | packages: 3 | - path: lib 4 | -------------------------------------------------------------------------------- /test/fixtures/minitest_recorder/appmap.yml: -------------------------------------------------------------------------------- 1 | name: minitest_recorder 2 | packages: 3 | - path: . 4 | -------------------------------------------------------------------------------- /test/fixtures/mocha_mock_app/lib/sheep.rb: -------------------------------------------------------------------------------- 1 | class Sheep 2 | def baa 3 | 'baa' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/fixtures/openssl_recorder/appmap.yml: -------------------------------------------------------------------------------- 1 | name: openssl_recorder 2 | packages: 3 | - path: lib 4 | -------------------------------------------------------------------------------- /examples/mock_webapp/Gemfile: -------------------------------------------------------------------------------- 1 | gem 'appmap', git: '../..', branch: `git rev-parse --abbrev-ref HEAD`.strip 2 | -------------------------------------------------------------------------------- /lib/appmap/depends.rb: -------------------------------------------------------------------------------- 1 | require 'appmap/depends/configuration' 2 | require 'appmap/depends/rake_tasks' 3 | -------------------------------------------------------------------------------- /lib/appmap/gem_hooks/activejob-cancel.yml: -------------------------------------------------------------------------------- 1 | - method: ActiveJob::Cancel#cancel 2 | label: job.cancel 3 | -------------------------------------------------------------------------------- /lib/appmap/gem_hooks/pandoc-ruby.yml: -------------------------------------------------------------------------------- 1 | - method: PandocRuby#convert 2 | labels: 3 | - system.exec.safe 4 | -------------------------------------------------------------------------------- /lib/appmap/gem_hooks/rails.yml: -------------------------------------------------------------------------------- 1 | - method: Rails::Application#config_for 2 | label: deserialize.safe 3 | -------------------------------------------------------------------------------- /lib/appmap/swagger.rb: -------------------------------------------------------------------------------- 1 | require 'appmap/swagger/configuration' 2 | require 'appmap/swagger/rake_tasks' 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | vendor 2 | node_modules 3 | spec/fixtures/rails*_users_app 4 | spec/fixtures/rack_users_app 5 | -------------------------------------------------------------------------------- /test/fixtures/rspec_recorder/lib/hello.rb: -------------------------------------------------------------------------------- 1 | class Hello 2 | def say_hello 3 | 'Hello!' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/appmap/builtin_hooks/logger.yml: -------------------------------------------------------------------------------- 1 | - method: Logger::LogDevice#write 2 | require_name: logger 3 | label: log 4 | -------------------------------------------------------------------------------- /test/fixtures/minitest_recorder/lib/hello.rb: -------------------------------------------------------------------------------- 1 | class Hello 2 | def say_hello 3 | 'Hello!' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/depends/app/models/show.rb: -------------------------------------------------------------------------------- 1 | # dummy 2 | # This file is just here so it can be touched and trigger re-test. 3 | -------------------------------------------------------------------------------- /spec/fixtures/depends/app/models/user.rb: -------------------------------------------------------------------------------- 1 | # dummy 2 | # This file is just here so it can be touched and trigger re-test. 3 | -------------------------------------------------------------------------------- /spec/fixtures/depends/spec/api_spec.rb: -------------------------------------------------------------------------------- 1 | # dummy 2 | # This file is just here so it can be touched and trigger re-test. 3 | -------------------------------------------------------------------------------- /spec/fixtures/depends/spec/user_spec.rb: -------------------------------------------------------------------------------- 1 | # dummy 2 | # This file is just here so it can be touched and trigger re-test. 3 | -------------------------------------------------------------------------------- /test/fixtures/bundle_vendor_app/appmap.yml: -------------------------------------------------------------------------------- 1 | name: bundle_vendor_app 2 | packages: 3 | - gem: gli 4 | - gem: hacer 5 | -------------------------------------------------------------------------------- /Dockerfile.pg: -------------------------------------------------------------------------------- 1 | FROM postgres:10 2 | 3 | HEALTHCHECK --interval=1s --retries=10 CMD psql -U postgres -c "select 1" || exit 1 4 | -------------------------------------------------------------------------------- /lib/appmap/gem_hooks/sprockets.yml: -------------------------------------------------------------------------------- 1 | - method: Sprockets::EncodingUtils#unmarshaled_deflated 2 | label: deserialize.safe 3 | -------------------------------------------------------------------------------- /spec/fixtures/depends/app/models/api_key.rb: -------------------------------------------------------------------------------- 1 | # dummy 2 | # This file is just here so it can be touched and trigger re-test. 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | DisabledByDefault: true 3 | Exclude: 4 | - 'lib/**' 5 | - 'test/**' 6 | - 'spec/**' 7 | -------------------------------------------------------------------------------- /spec/fixtures/depends/app/models/configuration.rb: -------------------------------------------------------------------------------- 1 | # dummy 2 | # This file is just here so it can be touched and trigger re-test. 3 | -------------------------------------------------------------------------------- /spec/fixtures/hook/attr_accessor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AttrAccessor 4 | attr_accessor :value 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/hook/pkg_a/a.rb: -------------------------------------------------------------------------------- 1 | module PkgA 2 | class A 3 | def self.hello 4 | 'hello' 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/appmap/gem_hooks/railties.yml: -------------------------------------------------------------------------------- 1 | - method: Rails::Application::Configuration#database_configuration 2 | label: deserialize.safe 3 | -------------------------------------------------------------------------------- /lib/appmap/recording_methods.rb: -------------------------------------------------------------------------------- 1 | module AppMap 2 | RECORDING_METHODS = %i[rspec minitest cucumber remote requests].freeze 3 | end 4 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /test/fixtures/mocha_mock_app/appmap.yml: -------------------------------------------------------------------------------- 1 | name: mocha_mock_app 2 | packages: 3 | - path: lib 4 | - gem: mocha 5 | shallow: false 6 | -------------------------------------------------------------------------------- /lib/appmap/gem_hooks/resque.yml: -------------------------------------------------------------------------------- 1 | - method: Resque::Job.create 2 | label: job.create 3 | - method: Resque::Job.destroy 4 | label: job.cancel 5 | -------------------------------------------------------------------------------- /spec/fixtures/depends/app/controllers/api/api_keys_controller.rb: -------------------------------------------------------------------------------- 1 | # dummy 2 | # This file is just here so it can be touched and trigger re-test. 3 | -------------------------------------------------------------------------------- /spec/fixtures/depends/app/controllers/organizations_controller.rb: -------------------------------------------------------------------------------- 1 | # dummy 2 | # This file is just here so it can be touched and trigger re-test. 3 | -------------------------------------------------------------------------------- /lib/appmap/builtin_hooks/net/http.yml: -------------------------------------------------------------------------------- 1 | - method: Net::HTTP#request 2 | label: protocol.http 3 | handler_class: AppMap::Handler::NetHTTPHandler 4 | -------------------------------------------------------------------------------- /lib/appmap/gem_hooks/sidekiq.yml: -------------------------------------------------------------------------------- 1 | - method: Sidekiq::Client#push 2 | label: job.create 3 | - method: Sidekiq::Job#delete 4 | label: job.cancel 5 | -------------------------------------------------------------------------------- /spec/fixtures/hook/labels.rb: -------------------------------------------------------------------------------- 1 | # @label has-cls-label 2 | class ClassWithLabel 3 | # @label has-fn-label 4 | def fn_with_label 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/features/support/env.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'cucumber/rails' 4 | require 'appmap/cucumber' 5 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/features/support/env.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'cucumber/rails' 4 | require 'appmap/cucumber' 5 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/bin/importmap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../config/application" 4 | require "importmap/commands" 5 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /test/fixtures/minitest_recorder/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/tagz' 2 | Minitest::Tagz.choose_tags(*ENV['TAGS'].split(',')) if ENV['TAGS'] 3 | -------------------------------------------------------------------------------- /spec/fixtures/hook/sub_packages.rb: -------------------------------------------------------------------------------- 1 | require_relative 'pkg_a/a' 2 | 3 | module SubPackages 4 | def self.invoke_a 5 | PkgA::A.hello 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/fixtures/cucumber4_recorder/lib/hello.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Hello 4 | def say_hello 5 | 'Hello!' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/fixtures/cucumber_recorder/lib/hello.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Hello 4 | def say_hello 5 | 'Hello!' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/fixtures/hook/constructor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Constructor 4 | def initialize(value) 5 | @value = value 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 4 | 5 | gemspec 6 | 7 | gem 'rack', '~> 2' 8 | -------------------------------------------------------------------------------- /test/fixtures/cucumber4_recorder/features/say_hello.feature: -------------------------------------------------------------------------------- 1 | Feature: I can say hello 2 | 3 | Scenario: Say hello 4 | When I say hello 5 | Then the message is hello 6 | -------------------------------------------------------------------------------- /test/fixtures/cucumber_recorder/features/say_hello.feature: -------------------------------------------------------------------------------- 1 | Feature: I can say hello 2 | 3 | Scenario: Say hello 4 | When I say hello 5 | Then the message is hello 6 | -------------------------------------------------------------------------------- /test/fixtures/openssl_recorder/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'appmap', git: 'applandinc/appmap-ruby', branch: `git rev-parse --abbrev-ref HEAD`.strip 4 | -------------------------------------------------------------------------------- /lib/appmap/gem_hooks/activejob.yml: -------------------------------------------------------------------------------- 1 | - method: ActiveJob::Enqueuing#enqueue 2 | label: job.create 3 | - methods: 4 | - ActiveJob::Execution#perform_now 5 | label: job.perform 6 | -------------------------------------------------------------------------------- /test/fixtures/process_recorder/hello.rb: -------------------------------------------------------------------------------- 1 | require 'appmap/record' 2 | 3 | class Hello 4 | def say_hello 5 | 'Hello!' 6 | end 7 | end 8 | 9 | Hello.new.say_hello 10 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/app/controllers/health_controller.rb: -------------------------------------------------------------------------------- 1 | class HealthController < ActionController::API 2 | def show 3 | render nothing: true, status: 204 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/app/controllers/health_controller.rb: -------------------------------------------------------------------------------- 1 | class HealthController < ActionController::API 2 | def show 3 | render nothing: true, status: 204 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/controllers/health_controller.rb: -------------------------------------------------------------------------------- 1 | class HealthController < ActionController::API 2 | def show 3 | render nothing: true, status: 204 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/depends/test/actual_minitest_test.rb: -------------------------------------------------------------------------------- 1 | class ActualMinitestTest < Minitest::Test 2 | def test_object_to_s 3 | expect(subject.to_s).to be_instance_of(String) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /test/fixtures/cucumber4_recorder/features/support/env.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'cucumber' 4 | require 'appmap/cucumber' 5 | require File.join(__dir__, '../../lib/hello') 6 | -------------------------------------------------------------------------------- /test/fixtures/cucumber_recorder/features/support/env.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'cucumber' 4 | require 'appmap/cucumber' 5 | require File.join(__dir__, '../../lib/hello') 6 | -------------------------------------------------------------------------------- /exe/appmap-index: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | $LOAD_PATH.unshift File.join(__dir__, '../lib') 5 | 6 | require 'appmap/command/index' 7 | AppMap::Command::Index.run 8 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /test/fixtures/cli_record_test/lib/cli_record_test/main.rb: -------------------------------------------------------------------------------- 1 | class Main 2 | class << self 3 | def say_hello 4 | puts 'Hello' 5 | end 6 | end 7 | end 8 | 9 | Main.say_hello 10 | -------------------------------------------------------------------------------- /exe/appmap-inspect: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | $LOAD_PATH.unshift File.join(__dir__, '../lib') 5 | 6 | require 'appmap/command/inspect' 7 | AppMap::Command::Inspect.run 8 | -------------------------------------------------------------------------------- /lib/appmap/gem_hooks/jwt.yml: -------------------------------------------------------------------------------- 1 | - method: JWT#decode 2 | label: jwt.decode 3 | 4 | - method: JWT#encode 5 | label: jwt.encode 6 | 7 | - method: JWT::Signature#verify 8 | label: jwt.signature.verify 9 | -------------------------------------------------------------------------------- /spec/fixtures/hook/compare.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/security_utils' 2 | 3 | class Compare 4 | def self.compare(s1, s2) 5 | ActiveSupport::SecurityUtils.secure_compare(s1, s2) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/fixtures/mocha_mock_app/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'appmap', git: 'applandinc/appmap-ruby', branch: `git rev-parse --abbrev-ref HEAD`.strip 4 | gem 'minitest' 5 | gem 'mocha' 6 | -------------------------------------------------------------------------------- /test/fixtures/rspec_recorder/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'appmap', git: 'applandinc/appmap-ruby', branch: `git rev-parse --abbrev-ref HEAD`.strip 4 | gem 'byebug' 5 | gem 'rspec' 6 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/app/views/users/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1{style: 'margin-top: 2em'} 2 | = pluralize @users.size, 'user' 3 | 4 | %ul 5 | - for user in @users 6 | %li 7 | = user.login 8 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/app/views/users/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1{style: 'margin-top: 2em'} 2 | = pluralize @users.size, 'user' 3 | 4 | %ul 5 | - for user in @users 6 | %li 7 | = user.login 8 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rails6_users_app", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "devDependencies": { 6 | "@appland/appmap": "^3.18.3" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/views/users/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1{style: 'margin-top: 2em'} 2 | = pluralize @users.size, 'user' 3 | 4 | %ul 5 | - for user in @users 6 | %li 7 | = user.login 8 | -------------------------------------------------------------------------------- /test/fixtures/cucumber_recorder/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'appmap', git: 'applandinc/appmap-ruby', branch: `git rev-parse --abbrev-ref HEAD`.strip 4 | gem 'byebug' 5 | gem 'cucumber', '< 4' 6 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | //= link_tree ../../javascript .js 4 | //= link_tree ../../../vendor/javascript .js 5 | -------------------------------------------------------------------------------- /test/fixtures/cucumber4_recorder/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'appmap', git: 'applandinc/appmap-ruby', branch: `git rev-parse --abbrev-ref HEAD`.strip 4 | gem 'byebug' 5 | gem 'cucumber', '>= 4' 6 | -------------------------------------------------------------------------------- /test/util_test.rb: -------------------------------------------------------------------------------- 1 | 2 | # Should not be selected as the exception path 3 | # "location": "/home/runner/work/sample_app_6th_ed/sample_app_6th_ed/vendor/bundle/ruby/3.0.0/gems/spring-2.1.0/lib/spring/application.rb:301" 4 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/javascript/application.js: -------------------------------------------------------------------------------- 1 | // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails 2 | import "@hotwired/turbo-rails" 3 | import "controllers" 4 | -------------------------------------------------------------------------------- /spec/fixtures/hook/report_parameters.rb: -------------------------------------------------------------------------------- 1 | 2 | class ReportParameters 3 | def to_s; self.class.name; end 4 | 5 | def report_parameters(*args, kw1:, kw2: 'kw2', **kws) 6 | method(:report_parameters).parameters 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | pg: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile.pg 7 | ports: 8 | - "5432" 9 | environment: 10 | POSTGRES_HOST_AUTH_METHOD: trust 11 | -------------------------------------------------------------------------------- /spec/fixtures/hook/job.rb: -------------------------------------------------------------------------------- 1 | class Job 2 | # @label job.perform 3 | def perform 4 | do_work 5 | end 6 | 7 | protected_methods 8 | 9 | def do_work 10 | puts "Doing work" 11 | sleep 0.001 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/fixtures/gem_test/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'appmap', git: 'applandinc/appmap-ruby', branch: `git rev-parse --abbrev-ref HEAD`.strip 4 | gem 'activesupport' 5 | gem 'byebug' 6 | gem 'minitest' 7 | gem 'parser' 8 | -------------------------------------------------------------------------------- /test/fixtures/minitest_recorder/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'appmap', git: 'applandinc/appmap-ruby', branch: `git rev-parse --abbrev-ref HEAD`.strip 4 | gem 'byebug' 5 | gem 'minitest' 6 | 7 | gem 'minitest-tagz' 8 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/app/views/layouts/application.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ 5 | %title Rails Users App 6 | %body 7 | = yield 8 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/app/views/layouts/application.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ 5 | %title Rails Users App 6 | %body 7 | = yield 8 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/views/layouts/application.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ 5 | %title Rails Users App 6 | %body 7 | = yield 8 | -------------------------------------------------------------------------------- /lib/appmap/gem_hooks/cancancan.yml: -------------------------------------------------------------------------------- 1 | - methods: 2 | - CanCan::ControllerAdditions#authorize! 3 | - CanCan::ControllerAdditions#can? 4 | - CanCan::ControllerAdditions#cannot? 5 | - CanCan::Ability#authorize? 6 | label: security.authorization 7 | -------------------------------------------------------------------------------- /spec/fixtures/hook/kwargs.rb: -------------------------------------------------------------------------------- 1 | class Kwargs 2 | class << self 3 | def no_kwargs(args) 4 | args 5 | end 6 | 7 | def has_kwrest_calls_no_kwargs(args, **kwargs) 8 | no_kwargs(**kwargs) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/jobs/print_user_count_job.rb: -------------------------------------------------------------------------------- 1 | class PrintUserCountJob < ApplicationJob 2 | FILENAME = "tmp/user_count.txt" 3 | 4 | def perform 5 | count = User.count 6 | File.write FILENAME, count 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/fixtures/rspec_recorder/spec/failed_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'appmap/rspec' 3 | require 'hello' 4 | 5 | describe Hello do 6 | it 'expectation fails' do 7 | expect(Hello.new.say_hello).to eq('Hello') 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/fixtures/rspec_recorder/spec/plain_hello_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'appmap/rspec' 3 | require 'hello' 4 | 5 | describe Hello do 6 | it 'says hello' do 7 | expect(Hello.new.say_hello).to eq('Hello!') 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/appmap/builtin_hooks/json.yml: -------------------------------------------------------------------------------- 1 | - method: JSON::Ext::Parser#parse 2 | labels: 3 | - format.json.parse 4 | - deserialize.safe 5 | - method: JSON::Ext::Generator::State#generate 6 | labels: 7 | - format.json.generate 8 | - serialize 9 | -------------------------------------------------------------------------------- /spec/fixtures/hook/custom_instance_method.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CustomInstanceMethod 4 | def to_s 5 | 'CustomInstance Method fixture' 6 | end 7 | 8 | def say_default 9 | 'default' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/fixtures/hook/method_named_call.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class MethodNamedCall 4 | def to_s 5 | 'MethodNamedCall' 6 | end 7 | 8 | def call(a, b, c, d, e) 9 | [ a, b, c, d, e ].join(' ') 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/fixtures/bundle_vendor_app/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'gli' 4 | gem 'hacer' 5 | 6 | appmap_gem_opts = {} 7 | appmap_gem_opts[:path] = '../../..' if File.exist?('../../../appmap.gemspec') 8 | gem 'appmap', appmap_gem_opts 9 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/javascript/controllers/hello_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus" 2 | 3 | export default class extends Controller { 4 | connect() { 5 | this.element.textContent = "Hello World!" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /spec/fixtures/depends/spec/actual_rspec_test.rb: -------------------------------------------------------------------------------- 1 | # This test has actual behavior because it's used to test the spec runner 2 | 3 | describe Object do 4 | it 'to_s returns a representation of the object' do 5 | expect(subject.to_s).to be_instance_of(String) 6 | end 7 | end -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: app_production 11 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /test/fixtures/cucumber4_recorder/features/support/steps.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | When('I say hello') do 4 | @message = Hello.new.say_hello 5 | end 6 | 7 | Then('the message is hello') do 8 | raise 'Wrong message!' unless @message == 'Hello!' 9 | end 10 | -------------------------------------------------------------------------------- /test/fixtures/cucumber_recorder/features/support/steps.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | When('I say hello') do 4 | @message = Hello.new.say_hello 5 | end 6 | 7 | Then('the message is hello') do 8 | raise 'Wrong message!' unless @message == 'Hello!' 9 | end 10 | -------------------------------------------------------------------------------- /test/fixtures/rspec_recorder/spec/labeled_hello_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'appmap/rspec' 3 | require 'hello' 4 | 5 | describe Hello, appmap: 'hello' do 6 | it 'says hello', appmap: 'speak' do 7 | expect(Hello.new.say_hello).to eq('Hello!') 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/fixtures/hook/prepended_override.rb: -------------------------------------------------------------------------------- 1 | 2 | module PrependedModule 3 | def say_hello 4 | 'please allow me to ' + super 5 | end 6 | end 7 | 8 | class PrependedClass 9 | prepend PrependedModule 10 | 11 | def say_hello 12 | 'introduce myself' 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /examples/mock_webapp/lib/mock_webapp/request.rb: -------------------------------------------------------------------------------- 1 | module MockWebapp 2 | RequestStruct = Struct.new(:params) 3 | 4 | # Mock request. 5 | # @appmap 6 | class Request < RequestStruct 7 | # @appmap 8 | def initialize(*args) 9 | super 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/fixtures/database.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | url: <%= ENV['DATABASE_URL'] %> 3 | adapter: postgresql 4 | database: <%= ENV['TEST_DATABASE'] || 'appland-rails7-test' %> 5 | 6 | development: 7 | <<: *default 8 | test: 9 | <<: *default 10 | production: 11 | <<: *default 12 | -------------------------------------------------------------------------------- /spec/fixtures/rack_users_app/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rack' 4 | gem 'rack-app' 5 | gem 'rspec' 6 | gem 'pry-byebug' 7 | 8 | appmap_gem_opts = {} 9 | appmap_gem_opts[:path] = '../../..' if File.exist?('../../../appmap.gemspec') 10 | gem 'appmap', appmap_gem_opts 11 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/db/migrate/20190728211408_create_users.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | change do 3 | create_table :users do 4 | primary_key :id 5 | text :login, null: false, unique: true 6 | column :password_digest, :bytea 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/db/migrate/20190728211408_create_users.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | change do 3 | create_table :users do 4 | primary_key :id 5 | text :login, null: false, unique: true 6 | column :password_digest, :bytea 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/test/controllers/functional_calc_test.rb: -------------------------------------------------------------------------------- 1 | # minitest_calc_test_unit_format_spec.rb 2 | 3 | require "minitest/autorun" 4 | 5 | class FunctionalCalcTest < Minitest::Test 6 | def test_add 7 | puts 'functional' 8 | assert_equal 2 + 2, 4 9 | end 10 | end -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/db/migrate/20190728211408_create_users.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | change do 3 | create_table :users do 4 | primary_key :id 5 | text :login, null: false, unique: true 6 | column :password_digest, :bytea 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/fixtures/hook/exclude.rb: -------------------------------------------------------------------------------- 1 | class ExcludeTest 2 | def instance_method 3 | 'instance_method' 4 | end 5 | 6 | class << self 7 | def singleton_method 8 | 'singleton_method' 9 | end 10 | end 11 | 12 | def self.cls_method 13 | 'class_method' 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/appmap/gem_hooks/random.yml: -------------------------------------------------------------------------------- 1 | - methods: 2 | - Random.new_seed 3 | - Random.rand 4 | - Random.raw_seed 5 | - Random.srand 6 | - Random#bytes 7 | - Random#rand 8 | labels: 9 | - pseudorandom 10 | - random.insecure 11 | - method: Random#seed 12 | label: pseudorandom.seed 13 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | namespace :api do 3 | resources :users, only: %i[index create] 4 | end 5 | 6 | resources :users, only: %i[index show] 7 | 8 | get 'health', to: 'health#show' 9 | 10 | root 'users#index' 11 | end 12 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | namespace :api do 3 | resources :users, only: %i[index create] 4 | end 5 | 6 | resources :users, only: %i[index show] 7 | 8 | get 'health', to: 'health#show' 9 | 10 | root 'users#index' 11 | end 12 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | namespace :api do 3 | resources :users, only: %i[index create] 4 | end 5 | 6 | resources :users, only: %i[index show] 7 | 8 | get 'health', to: 'health#show' 9 | 10 | root 'users#index' 11 | end 12 | -------------------------------------------------------------------------------- /spec/handler/class_with_eval.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # rubocop:disable Style/EvalWithLocation 4 | 5 | module AppMap 6 | class SpecClasses 7 | class WithEval 8 | eval %(def text; 'text'; end) 9 | end 10 | end 11 | end 12 | 13 | # rubocop:enable Style/EvalWithLocation 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /html/ 3 | .bundle 4 | /.yardoc 5 | /.yarn 6 | /_yardoc/ 7 | /coverage/ 8 | /doc/ 9 | /pkg/ 10 | /spec/reports/ 11 | tmp/ 12 | appmap_hook.log 13 | vendor 14 | node_modules 15 | Gemfile.lock 16 | appmap.json 17 | .vscode 18 | .byebug_history 19 | /lib/appmap/appmap.bundle 20 | *.so 21 | -------------------------------------------------------------------------------- /spec/fixtures/hook/proc_no_autosplat.rb: -------------------------------------------------------------------------------- 1 | # Since ruby 3.2, a proc that accepts a single positional argument and 2 | # keywords will no longer autosplat. 3 | class ProcNoAutosplat 4 | class << self 5 | def proc_no_autosplat(params) 6 | proc{|a, **k| a}.call(params) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/.gitattributes: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files. 2 | 3 | # Mark the database schema as having been generated. 4 | db/schema.rb linguist-generated 5 | 6 | # Mark any vendored files as having been vendored. 7 | vendor/* linguist-vendored 8 | -------------------------------------------------------------------------------- /test/fixtures/cucumber4_recorder/features/support/hooks.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'appmap' 4 | 5 | Around('not @appmap-disable') do |scenario, block| 6 | appmap = AppMap.record do 7 | block.call 8 | end 9 | 10 | AppMap::Cucumber.write_scenario(scenario, appmap) 11 | end 12 | -------------------------------------------------------------------------------- /test/fixtures/cucumber_recorder/features/support/hooks.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'appmap' 4 | 5 | Around('not @appmap-disable') do |scenario, block| 6 | appmap = AppMap.record do 7 | block.call 8 | end 9 | 10 | AppMap::Cucumber.write_scenario(scenario, appmap) 11 | end 12 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /lib/appmap/gem_hooks/devise.yml: -------------------------------------------------------------------------------- 1 | - method: Devise::TokenGenerator#generate 2 | label: secret 3 | - method: Devise.friendly_token 4 | label: secret 5 | - method: Devise.secure_compare 6 | label: security.secure_compare 7 | - method: Devise::Strategies::DatabaseAuthenticatable#authenticate! 8 | label: security.authentication 9 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/appmap.yml: -------------------------------------------------------------------------------- 1 | name: rails5_users_app 2 | packages: 3 | - path: app/models 4 | labels: [ mvc.model ] 5 | - path: app/controllers 6 | labels: [ mvc.controller ] 7 | - gem: sequel 8 | functions: 9 | - name: logger/Logger::LogDevice#write 10 | builtin: true 11 | label: log 12 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/javascript/controllers/application.js: -------------------------------------------------------------------------------- 1 | import { Application } from "@hotwired/stimulus" 2 | 3 | const application = Application.start() 4 | 5 | // Configure Stimulus development experience 6 | application.debug = false 7 | window.Stimulus = application 8 | 9 | export { application } 10 | -------------------------------------------------------------------------------- /test/fixtures/minitest_recorder/test/hello_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'minitest/autorun' 5 | require 'appmap/minitest' 6 | require 'hello' 7 | 8 | class HelloTest < ::Minitest::Test 9 | def test_hello 10 | assert_equal 'Hello!', Hello.new.say_hello 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/features/support/hooks.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | if AppMap::Cucumber.enabled? 4 | Around('not @appmap-disable') do |scenario, block| 5 | appmap = AppMap.record do 6 | block.call 7 | end 8 | 9 | AppMap::Cucumber.write_scenario(scenario, appmap) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/features/support/hooks.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | if AppMap::Cucumber.enabled? 4 | Around('not @appmap-disable') do |scenario, block| 5 | appmap = AppMap.record do 6 | block.call 7 | end 8 | 9 | AppMap::Cucumber.write_scenario(scenario, appmap) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/fixtures/gem_test/test/parser_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'appmap/minitest' 5 | require 'minitest/autorun' 6 | require 'parser/current' 7 | 8 | class ParserTest < ::Minitest::Test 9 | def test_parser 10 | Parser::CurrentRuby.parse(File.read(__FILE__)) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/appmap/builtin_hooks/open3.yml: -------------------------------------------------------------------------------- 1 | - methods: 2 | - Open3#capture2 3 | - Open3#capture2e 4 | - Open3#capture3 5 | - Open3#pipeline 6 | - Open3#pipeline_r 7 | - Open3#pipeline_rw 8 | - Open3#pipeline_start 9 | - Open3#pipeline_w 10 | - Open3#popen2 11 | - Open3#popen2e 12 | - Open3#popen3 13 | label: system.exec 14 | -------------------------------------------------------------------------------- /test/fixtures/minitest_recorder/test/hello_failed_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'minitest/autorun' 5 | require 'appmap/minitest' 6 | require 'hello' 7 | 8 | class HelloFailedTest < ::Minitest::Test 9 | def test_failed 10 | assert_equal 'Bye!', Hello.new.say_hello 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/test/integration/integration_calc_test.rb: -------------------------------------------------------------------------------- 1 | # minitest_calc_test_unit_format_spec.rb 2 | 3 | require "minitest/autorun" 4 | 5 | class IntegrationCalcTest < Minitest::Test 6 | class CalcTest < Minitest::Test 7 | def test_add 8 | puts 'integration' 9 | assert_equal 2 + 2, 4 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /.releaserc.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | - '@semantic-release/commit-analyzer' 3 | - '@semantic-release/release-notes-generator' 4 | - '@semantic-release/changelog' 5 | - 'semantic-release-rubygem' 6 | - - '@semantic-release/git' 7 | - assets: 8 | - CHANGELOG.md 9 | - appmap.gemspec 10 | - lib/appmap/version.rb 11 | - '@semantic-release/github' 12 | -------------------------------------------------------------------------------- /examples/mock_webapp/exe/mock_webapp_request: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $LOAD_PATH.unshift File.join(__dir__, '..', 'lib') 4 | 5 | require 'json' 6 | require 'mock_webapp/user' 7 | require 'mock_webapp/request' 8 | require 'mock_webapp/controller' 9 | 10 | include MockWebapp 11 | 12 | puts JSON.pretty_generate Controller.instance.process(Request.new(id: 'alice')) 13 | -------------------------------------------------------------------------------- /test/inspect_cli_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | class InspectCLITest < Minitest::Test 7 | def test_help 8 | output = `./exe/appmap-inspect --help` 9 | assert_equal 0, $CHILD_STATUS.exitstatus 10 | assert_includes output, 'Search AppMaps for references to a code object' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/appmap/command_error.rb: -------------------------------------------------------------------------------- 1 | module AppMap 2 | # Raised when a system / shell command fails. 3 | class CommandError < StandardError 4 | attr_reader :command, :msg 5 | 6 | def initialize(command, msg = nil) 7 | super [ "Command failed: #{command}", msg ].compact.join('; ') 8 | 9 | @command = command 10 | @msg = msg 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/fixtures/config/maximal_config.yml: -------------------------------------------------------------------------------- 1 | name: appmap 2 | packages: 3 | - path: app/models 4 | exclude: app/models/helpers 5 | - gem: rack 6 | - gem: activesupport 7 | shallow: false 8 | functions: 9 | - methods: 10 | - OpenSSL::X509::Request#sign 11 | - OpenSSL::X509::Request#verify 12 | label: crypto.x509 13 | language: ruby 14 | appmap_dir: tmp/appmap 15 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../lib', __dir__) 2 | 3 | # Disable default initialization of AppMap 4 | ENV['APPMAP_INITIALIZE'] = 'false' 5 | ENV.delete('RAILS_ENV') 6 | ENV.delete('APP_ENV') 7 | 8 | require 'appmap' 9 | 10 | require 'minitest/autorun' 11 | require 'diffy' 12 | require 'active_support' 13 | require 'active_support/core_ext' 14 | require 'json' 15 | -------------------------------------------------------------------------------- /lib/appmap/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module AppMap 4 | URL = 'https://github.com/applandinc/appmap-ruby' 5 | 6 | VERSION = '1.1.1' 7 | 8 | APPMAP_FORMAT_VERSION = '1.12.0' 9 | 10 | SUPPORTED_RUBY_VERSIONS = %w[2.5 2.6 2.7 3.0 3.1 3.2 3.3].freeze 11 | 12 | DEFAULT_APPMAP_DIR = 'tmp/appmap'.freeze 13 | DEFAULT_CONFIG_FILE_PATH = 'appmap.yml'.freeze 14 | end 15 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/appmap.yml: -------------------------------------------------------------------------------- 1 | name: rails6_users_app 2 | packages: 3 | - path: app/models 4 | labels: [ mvc.model ] 5 | - path: app/controllers 6 | labels: [ mvc.controller ] 7 | - gem: sequel 8 | functions: 9 | - name: logger/Logger::LogDevice#write 10 | builtin: true 11 | label: log 12 | swagger: 13 | project_version: 1.1.0 14 | output_dir: tmp/swagger 15 | -------------------------------------------------------------------------------- /spec/rails_test_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_spec_helper' 2 | 3 | describe 'Rails' do 4 | rails_versions.each do |rails_version| 5 | include_context 'rails app', rails_version 6 | 7 | it 'runs tests' do 8 | app.prepare_db 9 | app.run_cmd \ 10 | 'bundle exec rake', 11 | 'RAILS_ENV' => 'test', 12 | 'TEST_OPTS' => '--verbose' 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/appmap/builtin_hooks/psych.yml: -------------------------------------------------------------------------------- 1 | - methods: 2 | - Psych#parse 3 | - Psych#parse_stream 4 | label: format.yaml.parse 5 | - methods: 6 | - Psych#load 7 | - Psych#load_stream 8 | label: deserialize.unsafe 9 | - method: Psych#safe_load 10 | label: deserialize.safe 11 | - methods: 12 | - Psych#dump 13 | - Psych#dump_stream 14 | labels: 15 | - format.yaml.generate 16 | - serialize 17 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/config/importmap.rb: -------------------------------------------------------------------------------- 1 | # Pin npm packages by running ./bin/importmap 2 | 3 | pin "application", preload: true 4 | pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true 5 | pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true 6 | pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true 7 | pin_all_from "app/javascript/controllers", under: "controllers" 8 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/lib/hijacker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Rack middleware that changes the response 4 | # code to 422 if the query string is "hi" 5 | class Hijacker 6 | def initialize(app) 7 | @app = app 8 | end 9 | 10 | def call(env) 11 | @app.call(env).tap do |response| 12 | response[0] = 422 if env['QUERY_STRING'] == 'hi' 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /examples/mock_webapp/lib/mock_webapp/user.rb: -------------------------------------------------------------------------------- 1 | module MockWebapp 2 | UserStruct = Struct.new(:login) 3 | 4 | # Mock model object. 5 | # @appmap 6 | class User < UserStruct 7 | USERS = { 8 | 'alice' => User.new('alice') 9 | }.freeze 10 | 11 | class << self 12 | # @appmap 13 | def find(id) 14 | USERS[id] || raise("No such user #{id}") 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | -------------------------------------------------------------------------------- /spec/fixtures/hook/args_to_kwargs.rb: -------------------------------------------------------------------------------- 1 | # Since ruby 3.2, all methods wishing to delegate keyword arguments 2 | # through *args must now be marked with ruby2_keywords, with no 3 | # exception. 4 | class ArgsToKwArgs 5 | class << self 6 | def kw_rest(**kwargs) 7 | kwargs 8 | end 9 | 10 | # if ruby2_keywords is removed this test fails 11 | ruby2_keywords def has_args_calls_kwargs(*args) 12 | kw_rest(*args) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/depends/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require_relative '../spec_helper' 2 | 3 | DEPENDS_TEST_DIR = 'spec/fixtures/depends' 4 | DEPENDS_BASE_DIR = DEPENDS_TEST_DIR 5 | 6 | def update_appmap_index 7 | cmd = [ 8 | './exe/appmap-index', 9 | '--appmap-dir', 10 | DEPENDS_TEST_DIR 11 | ] 12 | if ENV['DEBUG'] == 'true' 13 | cmd << '--verbose' 14 | end 15 | 16 | system cmd.join(' ') or raise "Failed to update AppMap index in #{DEPENDS_TEST_DIR}" 17 | end 18 | 19 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/lib/tasks/count_users.rake: -------------------------------------------------------------------------------- 1 | require "active_job" 2 | 3 | task count_users: :environment do 4 | FileUtils.rm_f PrintUserCountJob::FILENAME 5 | 6 | # Queue the job 7 | PrintUserCountJob.perform_later 8 | 9 | # Wait for the job to complete 10 | until File.exist?(PrintUserCountJob::FILENAME) 11 | sleep 0.1 12 | end 13 | count = File.read(PrintUserCountJob::FILENAME).to_i 14 | puts "User count: #{count}" 15 | sleep 1 16 | end 17 | -------------------------------------------------------------------------------- /test/fixtures/minitest_recorder/test/hello_tagged_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'minitest/autorun' 7 | require 'appmap/minitest' 8 | require 'hello' 9 | 10 | class HelloTaggedTest < ::Minitest::Test 11 | tag :noappmap 12 | def test_tagged 13 | assert_equal 'Hello!', Hello.new.say_hello 14 | end 15 | 16 | def test_untagged 17 | assert_equal 'Hello!', Hello.new.say_hello 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be filtered from the log file. Use this to limit dissemination of 4 | # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported 5 | # notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 8 | ] 9 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/features/api_users.feature: -------------------------------------------------------------------------------- 1 | Feature: /api/users 2 | 3 | @appmap-disable 4 | Scenario: A user can be created 5 | When I create a user 6 | Then the response status should be 201 7 | 8 | Scenario: When a user is created, it should be in the user list 9 | Given I create a user 10 | And the response status should be 201 11 | When I list the users 12 | Then the response status should be 200 13 | And the response should include the user 14 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/features/api_users.feature: -------------------------------------------------------------------------------- 1 | Feature: /api/users 2 | 3 | @appmap-disable 4 | Scenario: A user can be created 5 | When I create a user 6 | Then the response status should be 201 7 | 8 | Scenario: When a user is created, it should be in the user list 9 | Given I create a user 10 | And the response status should be 201 11 | When I list the users 12 | Then the response status should be 200 13 | And the response should include the user 14 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Define an application-wide HTTP permissions policy. For further 2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy 3 | # 4 | # Rails.application.config.permissions_policy do |f| 5 | # f.camera :none 6 | # f.gyroscope :none 7 | # f.microphone :none 8 | # f.usb :none 9 | # f.fullscreen :self 10 | # f.payment :self, "https://secure.example.com" 11 | # end 12 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | fvHJWgAisxKdEtPejF1DMotrGxwK5QwUVQRqOvkI7+olbFTlcZMah2cfLAupIMKEZKXwx3ui+kNWDbMv1/JYxiMQfsvDZPx/yXD4k0IENYMILaMApFfxco5cjerEHCOZb+TJxOCTKGHD1ckt8zwNbBOA6mN1P8sbs3IXRc8bz/2FG/ILQvNIsvMO2ODb8WEeIp5N3dWkkk2pPJqwodBpoHHqcycTMru/pNl+V8BHEgfjMOIzdHpz2M1A2F4vOOCWx4vL2onZJXMDta3p9FZoypGZLr5X37+/B9fM+dJhWNz2Bvrlq8amdbKmOfSjehUAsSePj+nBY8zu0ZeNx4t7/3JBOKQ8PTSvovTgw71IeZckavz1dyyPatJ3d7ngrC7x2/VKt/JP1nAaf+5kBEjaARuM0+VewXZiO7Y5--ze5FclBM0tyGg/Ql--+S/6aIP52fpUTSoTQlw5nA== -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | fvHJWgAisxKdEtPejF1DMotrGxwK5QwUVQRqOvkI7+olbFTlcZMah2cfLAupIMKEZKXwx3ui+kNWDbMv1/JYxiMQfsvDZPx/yXD4k0IENYMILaMApFfxco5cjerEHCOZb+TJxOCTKGHD1ckt8zwNbBOA6mN1P8sbs3IXRc8bz/2FG/ILQvNIsvMO2ODb8WEeIp5N3dWkkk2pPJqwodBpoHHqcycTMru/pNl+V8BHEgfjMOIzdHpz2M1A2F4vOOCWx4vL2onZJXMDta3p9FZoypGZLr5X37+/B9fM+dJhWNz2Bvrlq8amdbKmOfSjehUAsSePj+nBY8zu0ZeNx4t7/3JBOKQ8PTSvovTgw71IeZckavz1dyyPatJ3d7ngrC7x2/VKt/JP1nAaf+5kBEjaARuM0+VewXZiO7Y5--ze5FclBM0tyGg/Ql--+S/6aIP52fpUTSoTQlw5nA== -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | 5gLkE9U+IodAvlLD/kreDusStGbdm/TH9immZVZ5UN61VW8l4niXNf6uo/UijJ6DIlNp5DBLyMlo9Mt4FCo7YARIHHTkuLF+XQFcHQMtZQbQFVqO3jzH2D3/qX5PugvXJOQfVuRyEQdhIxifotPVLgi3UtA/nUAafzyQcGG5I/OrRWIaLYjOn/nVis02Fvr4/882/cKMjmFRdwtPdksfNwI8ydvkz4LLMNyVhOnjm+3I8rPfkQ2REQtUaWqkotCNy7/DGg6afOuKVaPkrKvjtSXZg7m1mnHhDZE2NymQ3Or+0DTRRUq5J8TLcTs1v6kEWWjEdq/AXyCn8dfZzXlxqg311wXD168pS1DJE+tJofPnn5l7zsGPwMtZsq7Vv0EQktEOwjo99JOjut91aXpaX2Zxqvi64AaoZAcC--nogUUHkwnNTbEo9m--eYBMVM3TEj/+HE0GLooHbg== -------------------------------------------------------------------------------- /test/fixtures/mocha_mock_app/test/sheep_test.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../lib', __dir__) 2 | 3 | require 'minitest/autorun' 4 | 5 | require 'appmap' 6 | require 'appmap/minitest' if ENV['APPMAP_AUTOREQUIRE'] == 'false' 7 | 8 | require 'sheep' 9 | require 'mocha/minitest' 10 | 11 | class SheepTest < Minitest::Test 12 | def test_sheep 13 | sheep = mock('sheep') 14 | sheep.responds_like(Sheep.new) 15 | sheep.expects(:baa).returns('baa') 16 | sheep.baa 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | -------------------------------------------------------------------------------- /examples/mock_webapp/lib/mock_webapp/controller.rb: -------------------------------------------------------------------------------- 1 | module MockWebapp 2 | # Mock controller. 3 | # @appmap 4 | class Controller 5 | @controller = nil 6 | 7 | class << self 8 | # Singleton factory method. 9 | # 10 | # @appmap 11 | def instance 12 | @controller ||= Controller.new 13 | end 14 | end 15 | 16 | # @appmap 17 | def process(request) 18 | id = request.params[:id] 19 | user = User.find(id) 20 | user.to_h 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/spec/system/rack_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe 'Rack stack', type: :system do 6 | before do 7 | driven_by :rack_test 8 | end 9 | 10 | it 'changes the response on the index to 422' do 11 | visit '/users?hi' 12 | expect(page.status_code).to equal 422 13 | end 14 | 15 | it 'can serve sprocket assets' do 16 | visit '/assets/application.css' 17 | expect(page.status_code).to equal 200 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/appmap/handler/marshal_load_handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'appmap/handler/function_handler' 4 | 5 | module AppMap 6 | module Handler 7 | class MarshalLoadHandler < FunctionHandler 8 | PARAMETERS= [ 9 | [ :req, :source ], 10 | [ :rest ], 11 | ] 12 | 13 | def handle_call(receiver, args) 14 | AppMap::Event::MethodCall.build_from_invocation(defined_class, hook_method, receiver, args, parameters: PARAMETERS) 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/fixtures/hook/protected_method.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ProtectedMethod 4 | def call_protected 5 | protected_method 6 | end 7 | 8 | def to_s 9 | 'Protected Method fixture' 10 | end 11 | 12 | class << self 13 | def call_protected 14 | protected_method 15 | end 16 | 17 | protected 18 | 19 | def protected_method 20 | 'self.protected' 21 | end 22 | end 23 | 24 | protected 25 | 26 | def protected_method 27 | 'protected' 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/appmap/service/test_framework_detector.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module AppMap 4 | module Service 5 | class TestFrameworkDetector 6 | class << self 7 | def rspec_present? 8 | Gem.loaded_specs.has_key?('rspec-core') 9 | end 10 | 11 | def minitest_present? 12 | Gem.loaded_specs.has_key?('minitest') 13 | end 14 | 15 | def cucumber_present? 16 | Gem.loaded_specs.has_key?('cucumber') 17 | end 18 | end 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | def index 3 | @users = User.all 4 | end 5 | 6 | def show 7 | find_user = lambda do |id| 8 | if User.respond_to?(:[]) 9 | User[login: id] 10 | else 11 | User.find_by_login!(id) 12 | end 13 | end 14 | 15 | if (@user = find_user.(params[:id])) 16 | render plain: @user 17 | else 18 | render plain: 'Not found', status: 404 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | def index 3 | @users = User.all 4 | end 5 | 6 | def show 7 | find_user = lambda do |id| 8 | if User.respond_to?(:[]) 9 | User[login: id] 10 | else 11 | User.find_by_login!(id) 12 | end 13 | end 14 | 15 | if (@user = find_user.(params[:id])) 16 | render plain: @user 17 | else 18 | render plain: 'Not found', status: 404 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | def index 3 | @users = User.all 4 | end 5 | 6 | def show 7 | find_user = lambda do |id| 8 | if User.respond_to?(:[]) 9 | User[login: id] 10 | else 11 | User.find_by_login!(id) 12 | end 13 | end 14 | 15 | if (@user = find_user.(params[:id])) 16 | render plain: @user 17 | else 18 | render plain: 'Not found', status: 404 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # using bash wrapper as Rake blows up in `require/extentiontask` (line 10) 3 | 4 | RELEASE_FLAGS="" 5 | if [ ! -z "$TRAVIS_REPO_SLUG" ]; then 6 | RELEASE_FLAGS="-r git+https://github.com/${TRAVIS_REPO_SLUG}.git" 7 | fi 8 | 9 | if [ ! -z "$GEM_ALTERNATIVE_NAME" ]; then 10 | echo "Release: GEM_ALTERNATIVE_NAME=$GEM_ALTERNATIVE_NAME" 11 | else 12 | echo "No GEM_ALTERNATIVE_NAME is provided, releasing gem with default name ('appmap')" 13 | fi 14 | 15 | set -ex 16 | exec semantic-release $RELEASE_FLAGS 17 | -------------------------------------------------------------------------------- /lib/appmap/handler/open_ssl_handler.rb: -------------------------------------------------------------------------------- 1 | require 'appmap/handler/function_handler' 2 | 3 | module AppMap 4 | module Handler 5 | class OpenSSLHandler < FunctionHandler 6 | def handle_call(receiver, args) 7 | super.tap do |event| 8 | algorithm = receiver.name 9 | event.receiver[:labels] ||= [] 10 | label = [ 'crypto.algorithm', algorithm ].join('.') 11 | event.receiver[:labels] << label unless event.receiver[:labels].include?(label) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/appmap/service/guesser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module AppMap 4 | module Service 5 | class Guesser 6 | POSSIBLE_PATHS = %w[app lib] 7 | class << self 8 | def guess_name 9 | return Pathname.new(`git rev-parse --show-toplevel`.strip).basename.to_s if File.directory?('.git') 10 | 11 | Dir.pwd.split('/').last 12 | end 13 | 14 | def guess_paths 15 | POSSIBLE_PATHS.select { |path| File.directory?(path) } 16 | end 17 | end 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/features/support/steps.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | When 'I create a user' do 4 | @response = post '/api/users', login: 'alice' 5 | end 6 | 7 | Then(/the response status should be (\d+)/) do |status| 8 | expect(@response.status).to eq(status.to_i) 9 | end 10 | 11 | When 'I list the users' do 12 | @response = get '/api/users' 13 | @users = JSON.parse(@response.body) 14 | end 15 | 16 | Then 'the response should include the user' do 17 | expect(@users.map { |u| u['login'] }).to include('alice') 18 | end 19 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/features/support/steps.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | When 'I create a user' do 4 | @response = post '/api/users', login: 'alice' 5 | end 6 | 7 | Then(/the response status should be (\d+)/) do |status| 8 | expect(@response.status).to eq(status.to_i) 9 | end 10 | 11 | When 'I list the users' do 12 | @response = get '/api/users' 13 | @users = JSON.parse(@response.body) 14 | end 15 | 16 | Then 'the response should include the user' do 17 | expect(@users.map { |u| u['login'] }).to include('alice') 18 | end 19 | -------------------------------------------------------------------------------- /spec/open_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe AppMap::Open do 6 | context 'a block of Ruby code' do 7 | it 'opens in the browser' do 8 | appmap = AppMap.record do 9 | File.read __FILE__ 10 | end 11 | 12 | open = AppMap::Open.new(appmap) 13 | server = open.run_server 14 | page = Net::HTTP.get URI.parse("http://localhost:#{open.port}") 15 | expect(page).to include(%(name="data" value='{"version)) 16 | server.kill 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/fixtures/hook/instance_method.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class InstanceMethod 4 | def to_s 5 | 'Instance Method fixture' 6 | end 7 | 8 | def say_default 9 | 'default' 10 | end 11 | 12 | def say_echo(arg) 13 | arg.to_s 14 | end 15 | 16 | def say_kw(kw: 'kw') 17 | kw.to_s 18 | end 19 | 20 | def say_kws(*args, kw1:, kw2: 'kw2', **kws) 21 | [kw1, kw2, kws, args].join 22 | end 23 | 24 | def say_block(&block) 25 | yield 26 | end 27 | 28 | def say_the_time 29 | Time.now.to_s 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/app/models/activerecord/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | has_secure_password validations: false 3 | validates_presence_of :login 4 | validates_confirmation_of :password, if: -> { password_provided? } 5 | 6 | def authenticate(unencrypted) 7 | # Just be extra sure that empty passwords aren't accepted 8 | return false if unencrypted.blank? || password.blank? 9 | 10 | super 11 | end 12 | 13 | protected 14 | 15 | def password_provided? 16 | !(password.blank? && password_confirmation.blank?) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/app/models/activerecord/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | has_secure_password validations: false 3 | validates_presence_of :login 4 | validates_confirmation_of :password, if: -> { password_provided? } 5 | 6 | def authenticate(unencrypted) 7 | # Just be extra sure that empty passwords aren't accepted 8 | return false if unencrypted.blank? || password.blank? 9 | 10 | super 11 | end 12 | 13 | protected 14 | 15 | def password_provided? 16 | !(password.blank? && password_confirmation.blank?) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/models/activerecord/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | has_secure_password validations: false 3 | validates_presence_of :login 4 | validates_confirmation_of :password, if: -> { password_provided? } 5 | 6 | def authenticate(unencrypted) 7 | # Just be extra sure that empty passwords aren't accepted 8 | return false if unencrypted.blank? || password.blank? 9 | 10 | super 11 | end 12 | 13 | protected 14 | 15 | def password_provided? 16 | !(password.blank? && password_confirmation.blank?) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /ext/appmap/extconf.rb: -------------------------------------------------------------------------------- 1 | require "mkmf" 2 | 3 | 4 | $CFLAGS='-Werror ' + $CFLAGS 5 | 6 | # Per https://bugs.ruby-lang.org/issues/17865, 7 | # compound-token-split-by-macro was added in clang 12 and broke 8 | # compilation with some of the ruby headers. If the current compiler 9 | # supports the new warning, turn it off. 10 | new_warning = '-Wno-error=compound-token-split-by-macro' 11 | if try_cflags(new_warning) 12 | $CFLAGS += ' ' + new_warning 13 | end 14 | 15 | extension_name = "appmap" 16 | dir_config(extension_name) 17 | create_makefile(File.join(extension_name, extension_name)) 18 | -------------------------------------------------------------------------------- /lib/appmap/command/index.rb: -------------------------------------------------------------------------------- 1 | require 'appmap/version' 2 | require 'appmap/node_cli' 3 | 4 | module AppMap 5 | module Command 6 | class Index < AppMap::NodeCLI 7 | class << self 8 | def run 9 | command = Index.new(verbose: ENV['DEBUG'] == 'true') 10 | command.index(ARGV) 11 | end 12 | end 13 | 14 | def index(arguments) 15 | detect_nodejs 16 | 17 | arguments.unshift 'index' 18 | arguments.unshift APPMAP_JS 19 | arguments.unshift 'npx' 20 | 21 | exec(*arguments) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/appmap/command/inspect.rb: -------------------------------------------------------------------------------- 1 | require 'appmap/version' 2 | require 'appmap/node_cli' 3 | 4 | module AppMap 5 | module Command 6 | class Inspect < AppMap::NodeCLI 7 | class << self 8 | def run 9 | command = Inspect.new(verbose: ENV['DEBUG'] == 'true') 10 | command.inspect(ARGV) 11 | end 12 | end 13 | 14 | def inspect(arguments) 15 | detect_nodejs 16 | 17 | arguments.unshift 'inspect' 18 | arguments.unshift APPMAP_JS 19 | arguments.unshift 'npx' 20 | 21 | exec(*arguments) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/config/initializers/cors.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Avoid CORS issues when API is called from the frontend app. 4 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. 5 | 6 | # Read more: https://github.com/cyu/rack-cors 7 | 8 | # Rails.application.config.middleware.insert_before 0, Rack::Cors do 9 | # allow do 10 | # origins 'example.com' 11 | # 12 | # resource '*', 13 | # headers: :any, 14 | # methods: [:get, :post, :put, :patch, :delete, :options, :head] 15 | # end 16 | # end 17 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/config/initializers/cors.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Avoid CORS issues when API is called from the frontend app. 4 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. 5 | 6 | # Read more: https://github.com/cyu/rack-cors 7 | 8 | # Rails.application.config.middleware.insert_before 0, Rack::Cors do 9 | # allow do 10 | # origins 'example.com' 11 | # 12 | # resource '*', 13 | # headers: :any, 14 | # methods: [:get, :post, :put, :patch, :delete, :options, :head] 15 | # end 16 | # end 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "appmap-ruby", 3 | "version": "1.0.0", 4 | "description": "AppMap client agent for Ruby", 5 | "directories": { 6 | "lib": "lib", 7 | "test": "test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/applandinc/appmap-ruby.git" 12 | }, 13 | "author": "kgilpin@gmail.com", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/applandinc/appmap-ruby/issues" 17 | }, 18 | "homepage": "https://github.com/applandinc/appmap-ruby#readme", 19 | "dependencies": { 20 | "@appland/appmap": "^3.18.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/users_app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | 16 | 17 | .byebug_history 18 | 19 | # Ignore master key for decrypting credentials and more. 20 | /config/master.key 21 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/users_app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | 16 | 17 | .byebug_history 18 | 19 | # Ignore master key for decrypting credentials and more. 20 | /config/master.key 21 | -------------------------------------------------------------------------------- /exe/appmap-agent-init: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'optparse' 5 | require 'appmap' 6 | require 'appmap/command/agent_setup/init' 7 | 8 | @options = { config_file: AppMap::DEFAULT_CONFIG_FILE_PATH } 9 | 10 | OptionParser.new do |parser| 11 | parser.banner = 'Usage: appmap-agent-init [options]' 12 | 13 | description = "AppMap configuration file path (default: #{AppMap::DEFAULT_CONFIG_FILE_PATH})" 14 | parser.on('-c', '--config=FILEPATH', description) do |filepath| 15 | @options[:config_file] = filepath 16 | end 17 | end.parse! 18 | 19 | AppMap::Command::AgentSetup::Init.new(@options[:config_file]).perform 20 | -------------------------------------------------------------------------------- /lib/appmap/gem_hooks/actionview.yml: -------------------------------------------------------------------------------- 1 | - methods: 2 | - ActionView::Renderer#render 3 | - ActionView::TemplateRenderer#render 4 | - ActionView::PartialRenderer#render 5 | label: mvc.view 6 | handler_class: AppMap::Handler::Rails::Template::RenderHandler 7 | require_name: action_view 8 | - methods: 9 | - ActionView::Resolver#find_all 10 | - ActionView::Resolver#find_all_anywhere 11 | label: mvc.template.resolver 12 | handler_class: AppMap::Handler::Rails::Template::ResolverHandler 13 | require_name: action_view 14 | - methods: 15 | - ActionView::Helpers::SanitizeHelper#sanitize 16 | label: string.html_safe 17 | require_name: action_view 18 | -------------------------------------------------------------------------------- /spec/fixtures/hook/anonkwargs_passed.rb: -------------------------------------------------------------------------------- 1 | # Since ruby 3.2, anonymous keyword rest arguments can be passed as 2 | # arguments, instead of just used in method parameters. 3 | class AnonKwargsPassed 4 | class << self 5 | def kw_rest(**) 6 | 'kwargs' 7 | end 8 | 9 | def has_kw_rest_calls_kw_rest(args, **) 10 | kw_rest(**) 11 | end 12 | 13 | # there's no has_kw_rest_calls_kw_rest_first because by convention 14 | # keyword arguments are always last 15 | 16 | def kw_rest_last(args) 17 | args 18 | end 19 | 20 | def has_kw_rest_calls_kw_rest_last(*args, **) 21 | kw_rest_last(**) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /exe/appmap-agent-status: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'optparse' 5 | require 'appmap' 6 | require 'appmap/command/agent_setup/status' 7 | 8 | @options = { config_file: AppMap::DEFAULT_CONFIG_FILE_PATH } 9 | 10 | OptionParser.new do |parser| 11 | parser.banner = 'Usage: appmap-agent-status [options]' 12 | 13 | description = "AppMap configuration file path (default: #{AppMap::DEFAULT_CONFIG_FILE_PATH})" 14 | parser.on('-c', '--config=FILEPATH', description) do |filepath| 15 | @options[:config_file] = filepath 16 | end 17 | end.parse! 18 | 19 | AppMap::Command::AgentSetup::Status.new(@options[:config_file]).perform 20 | -------------------------------------------------------------------------------- /test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'appmap/rspec' 3 | require 'hello' 4 | 5 | describe Hello do 6 | before do 7 | # Trick appmap-ruby into thinking we're a Rails app. 8 | stub_const('Rails', double('rails', version: 'fake.0')) 9 | end 10 | 11 | # The order of these examples is important. The tests check the 12 | # appmap for 'says hello', and we want another example to get run 13 | # before it. 14 | it 'does not say goodbye' do 15 | expect(Hello.new.say_hello).not_to eq('Goodbye!') 16 | end 17 | 18 | it 'says hello' do 19 | expect(Hello.new.say_hello).to eq('Hello!') 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /exe/appmap-agent-validate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'optparse' 5 | require 'appmap' 6 | require 'appmap/command/agent_setup/validate' 7 | 8 | @options = { config_file: AppMap::DEFAULT_CONFIG_FILE_PATH } 9 | 10 | OptionParser.new do |parser| 11 | parser.banner = 'Usage: appmap-agent-validate [options]' 12 | 13 | description = "AppMap configuration file path (default: #{AppMap::DEFAULT_CONFIG_FILE_PATH})" 14 | parser.on('-c', '--config=FILEPATH', description) do |filepath| 15 | @options[:config_file] = filepath 16 | end 17 | end.parse! 18 | 19 | AppMap::Command::AgentSetup::Validate.new(@options[:config_file]).perform 20 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/spec/requests/users_spec.rb: -------------------------------------------------------------------------------- 1 | require 'swagger_helper' 2 | require 'appmap/rswag' 3 | 4 | describe 'Users' do 5 | path '/api/users' do 6 | post 'Creates a user' do 7 | consumes 'application/json' 8 | parameter name: :user, in: :body, schema: { 9 | type: :object, 10 | properties: { 11 | login: { type: :string }, 12 | password: { type: :string } 13 | }, 14 | required: %w[login password] 15 | } 16 | response 201, 'user created' do 17 | let(:user) { { login: 'alice', password: 'foobar' } } 18 | run_test! 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/spec/controllers/users_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'rack/test' 3 | 4 | RSpec.describe UsersController, type: :controller do 5 | render_views 6 | 7 | describe 'GET /users' do 8 | before do 9 | User.create login: 'alice' 10 | end 11 | it 'lists the users' do 12 | get :index 13 | expect(response).to be_ok 14 | end 15 | end 16 | 17 | describe 'GET /users/:login' do 18 | before do 19 | User.create login: 'alice' 20 | end 21 | 22 | it 'shows the user' do 23 | get :show, params: { id: 'alice' } 24 | expect(response).to be_ok 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/javascript/controllers/index.js: -------------------------------------------------------------------------------- 1 | // Import and register all your controllers from the importmap under controllers/* 2 | 3 | import { application } from "controllers/application" 4 | 5 | // Eager load all controllers defined in the import map under controllers/**/*_controller 6 | import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" 7 | eagerLoadControllersFrom("controllers", application) 8 | 9 | // Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) 10 | // import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" 11 | // lazyLoadControllersFrom("controllers", application) 12 | -------------------------------------------------------------------------------- /lib/appmap/handler/function_handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'appmap/event' 4 | require 'appmap/hook/method' 5 | 6 | module AppMap 7 | module Handler 8 | # Base handler class, will emit method call and return events. 9 | class FunctionHandler < Hook::Method 10 | def handle_call(receiver, args) 11 | AppMap::Event::MethodCall.build_from_invocation(defined_class, hook_method, receiver, args) 12 | end 13 | 14 | def handle_return(call_event_id, elapsed, return_value, exception) 15 | AppMap::Event::MethodReturn.build_from_invocation(call_event_id, return_value, exception, elapsed: elapsed) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/db/schema.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | change do 3 | create_table(:schema_migrations) do 4 | column :filename, "text", :null=>false 5 | 6 | primary_key [:filename] 7 | end 8 | 9 | create_table(:users) do 10 | primary_key :id 11 | column :login, "text", :null=>false 12 | column :password_digest, "bytea" 13 | 14 | index [:login], :name=>:users_login_key, :unique=>true 15 | end 16 | end 17 | end 18 | Sequel.migration do 19 | change do 20 | self << "SET search_path TO \"$user\", public" 21 | self << "INSERT INTO \"schema_migrations\" (\"filename\") VALUES ('20190728211408_create_users.rb')" 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/db/schema.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | change do 3 | create_table(:schema_migrations) do 4 | column :filename, "text", :null=>false 5 | 6 | primary_key [:filename] 7 | end 8 | 9 | create_table(:users) do 10 | primary_key :id 11 | column :login, "text", :null=>false 12 | column :password_digest, "bytea" 13 | 14 | index [:login], :name=>:users_login_key, :unique=>true 15 | end 16 | end 17 | end 18 | Sequel.migration do 19 | change do 20 | self << "SET search_path TO \"$user\", public" 21 | self << "INSERT INTO \"schema_migrations\" (\"filename\") VALUES ('20190728211408_create_users.rb')" 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/db/schema.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | change do 3 | create_table(:schema_migrations) do 4 | column :filename, "text", :null=>false 5 | 6 | primary_key [:filename] 7 | end 8 | 9 | create_table(:users) do 10 | primary_key :id 11 | column :login, "text", :null=>false 12 | column :password_digest, "bytea" 13 | 14 | index [:login], :name=>:users_login_key, :unique=>true 15 | end 16 | end 17 | end 18 | Sequel.migration do 19 | change do 20 | self << "SET search_path TO \"$user\", public" 21 | self << "INSERT INTO \"schema_migrations\" (\"filename\") VALUES ('20190728211408_create_users.rb')" 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/appmap/record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'appmap' 4 | require 'json' 5 | 6 | tracer = AppMap.tracing.trace 7 | 8 | at_exit do 9 | AppMap.tracing.delete(tracer) 10 | 11 | events = [].tap do |event_list| 12 | event_list << tracer.next_event.to_h while tracer.event? 13 | end 14 | 15 | metadata = AppMap.detect_metadata 16 | metadata[:recorder] = { 17 | name: 'record_process', 18 | type: 'process' 19 | } 20 | 21 | appmap = { 22 | 'version' => AppMap::APPMAP_FORMAT_VERSION, 23 | 'metadata' => metadata, 24 | 'classMap' => AppMap.class_map(tracer.event_methods), 25 | 'events' => events 26 | } 27 | AppMap::Util.write_appmap('appmap.json', appmap) 28 | end 29 | -------------------------------------------------------------------------------- /spec/service/integration_test_path_finder_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'appmap/service/integration_test_path_finder' 5 | 6 | describe AppMap::Service::IntegrationTestPathFinder do 7 | subject { described_class.new('./spec/fixtures/rails6_users_app/') } 8 | 9 | describe '.count' do 10 | it 'counts existing paths' do 11 | expect(subject.count_paths).to be(4) 12 | end 13 | end 14 | 15 | describe '.find' do 16 | it 'finds paths' do 17 | expect(subject.find).to eq({ 18 | rspec: %w[spec/controllers spec/requests], 19 | minitest: %w[test/controllers test/integration], 20 | cucumber: [] 21 | }) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/ruby_method_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe AppMap::Trace::RubyMethod do 6 | # These tests are mostly targeted towards Windows. Make sure no operating system errors go unhandled. 7 | describe :comment do 8 | # These methods use native implementations, and their source code files are not regular file paths. 9 | let(:methods) { [''.method(:unpack1), Kernel.instance_method(:eval)] } 10 | 11 | it 'properly handles invalid source file paths' do 12 | methods.each do |method| 13 | ruby_method = AppMap::Trace::RubyMethod.new(nil, nil, method, false) 14 | expect { ruby_method.comment }.not_to raise_error 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/app/models/sequel/user.rb: -------------------------------------------------------------------------------- 1 | class User < Sequel::Model(:users) 2 | plugin :secure_password, cost: 12, include_validations: false 3 | plugin :validation_helpers 4 | 5 | def authenticate(unencrypted) 6 | # Just be extra sure that empty passwords aren't accepted 7 | return false if unencrypted.blank? || password.blank? 8 | 9 | super 10 | end 11 | 12 | def validate 13 | super 14 | 15 | errors.add :password, 'doesn\'t match confirmation' if password_provided? && password != password_confirmation 16 | 17 | validates_presence %i[login] 18 | end 19 | 20 | protected 21 | 22 | def password_provided? 23 | !(password.blank? && password_confirmation.blank?) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/app/models/sequel/user.rb: -------------------------------------------------------------------------------- 1 | class User < Sequel::Model(:users) 2 | plugin :secure_password, cost: 12, include_validations: false 3 | plugin :validation_helpers 4 | 5 | def authenticate(unencrypted) 6 | # Just be extra sure that empty passwords aren't accepted 7 | return false if unencrypted.blank? || password.blank? 8 | 9 | super 10 | end 11 | 12 | def validate 13 | super 14 | 15 | errors.add :password, 'doesn\'t match confirmation' if password_provided? && password != password_confirmation 16 | 17 | validates_presence %i[login] 18 | end 19 | 20 | protected 21 | 22 | def password_provided? 23 | !(password.blank? && password_confirmation.blank?) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/spec/controllers/users_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'rack/test' 3 | 4 | RSpec.describe UsersController, type: :controller do 5 | render_views 6 | 7 | describe 'GET /users' do 8 | before do 9 | User.create login: 'alice' 10 | end 11 | it 'lists the users' do 12 | get :index 13 | expect(response).to be_ok 14 | end 15 | end 16 | 17 | describe 'GET /users/:login' do 18 | before do 19 | User.create login: 'alice' 20 | end 21 | 22 | it 'shows the user' do 23 | get :show, params: { id: 'alice' } 24 | expect(response).to be_ok 25 | end 26 | it 'performance test', appmap: false do 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/models/sequel/user.rb: -------------------------------------------------------------------------------- 1 | class User < Sequel::Model(:users) 2 | plugin :secure_password, cost: 12, include_validations: false 3 | plugin :validation_helpers 4 | 5 | def authenticate(unencrypted) 6 | # Just be extra sure that empty passwords aren't accepted 7 | return false if unencrypted.blank? || password.blank? 8 | 9 | super 10 | end 11 | 12 | def validate 13 | super 14 | 15 | errors.add :password, 'doesn\'t match confirmation' if password_provided? && password != password_confirmation 16 | 17 | validates_presence %i[login] 18 | end 19 | 20 | protected 21 | 22 | def password_provided? 23 | !(password.blank? && password_confirmation.blank?) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/spec/controllers/users_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'rack/test' 3 | 4 | RSpec.describe UsersController, type: :controller do 5 | render_views 6 | 7 | describe 'GET /users' do 8 | before do 9 | User.create login: 'alice' 10 | end 11 | it 'lists the users' do 12 | get :index 13 | expect(response).to be_ok 14 | end 15 | end 16 | 17 | describe 'GET /users/:login' do 18 | before do 19 | User.create login: 'alice' 20 | end 21 | 22 | it 'shows the user' do 23 | get :show, params: { id: 'alice' } 24 | expect(response).to be_ok 25 | end 26 | it 'performance test', appmap: false do 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/fixtures/openssl_recorder/lib/openssl_key_sign.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # From the manual page https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL.html 4 | 5 | require 'appmap' 6 | require 'openssl' 7 | require 'openssl/digest' 8 | 9 | module Example 10 | def Example.sign 11 | key = OpenSSL::PKey::RSA.new 2048 12 | 13 | document = 'the document' 14 | 15 | digest = OpenSSL::Digest::SHA256.new 16 | key.sign digest, document 17 | end 18 | end 19 | 20 | if __FILE__ == $0 21 | appmap = AppMap.record do 22 | Example.sign 23 | puts 'Computed signature' 24 | end 25 | appmap['metadata'] = [ 'recorder' => __FILE__ ] 26 | 27 | File.write('appmap.json', JSON.generate(appmap)) 28 | end 29 | -------------------------------------------------------------------------------- /spec/fixtures/hook/anonargs_passed.rb: -------------------------------------------------------------------------------- 1 | # Since ruby 3.2, anonymous rest arguments can be passed as arguments, 2 | # instead of just used in method parameters. 3 | class AnonArgsPassed 4 | class << self 5 | def anon_rest(*) 6 | 'anon' 7 | end 8 | 9 | def has_anon_rest_calls_anon_rest(*) 10 | anon_rest(*) 11 | end 12 | 13 | 14 | def non_anon_rest_first(*, arg1, arg2) 15 | [arg1, arg2] 16 | end 17 | 18 | def has_anon_rest_calls_non_anon_rest_first(*) 19 | non_anon_rest_first(*) 20 | end 21 | 22 | 23 | def non_anon_rest_last(arg1, arg2, *) 24 | [arg1, arg2] 25 | end 26 | 27 | def has_anon_rest_calls_non_anon_rest_last(*) 28 | non_anon_rest_last(*) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /lib/appmap/handler/rails/test_route.rb: -------------------------------------------------------------------------------- 1 | module AppMap 2 | module Handler 3 | module Rails 4 | class << self 5 | def test_route(app, request) 6 | app.matches?(request) 7 | rescue 8 | test_route_warn(request) 9 | false 10 | end 11 | 12 | protected 13 | 14 | @test_route_warned = false 15 | 16 | def test_route_warn(request) 17 | return if @test_route_warned 18 | 19 | @test_route_warned = true 20 | warn "Notice: Failed to match route for #{request&.path_info || "an unknown path"}: #{$ERROR_INFO}" 21 | warn "Notice: A solution for this problem is forthcoming, see https://github.com/getappmap/appmap-ruby/issues/360" 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a starting point to setup your application. 14 | # Add necessary setup steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | puts "\n== Removing old logs and tempfiles ==" 21 | system! 'bin/rails log:clear tmp:clear' 22 | 23 | puts "\n== Restarting application server ==" 24 | system! 'bin/rails restart' 25 | end 26 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a starting point to setup your application. 14 | # Add necessary setup steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | puts "\n== Removing old logs and tempfiles ==" 21 | system! 'bin/rails log:clear tmp:clear' 22 | 23 | puts "\n== Restarting application server ==" 24 | system! 'bin/rails restart' 25 | end 26 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a way to update your development environment automatically. 14 | # Add necessary update steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | puts "\n== Removing old logs and tempfiles ==" 21 | system! 'bin/rails log:clear tmp:clear' 22 | 23 | puts "\n== Restarting application server ==" 24 | system! 'bin/rails restart' 25 | end 26 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a way to update your development environment automatically. 14 | # Add necessary update steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | puts "\n== Removing old logs and tempfiles ==" 21 | system! 'bin/rails log:clear tmp:clear' 22 | 23 | puts "\n== Restarting application server ==" 24 | system! 'bin/rails restart' 25 | end 26 | -------------------------------------------------------------------------------- /spec/railtie_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_spec_helper' 2 | 3 | describe 'AppMap tracer via Railtie' do 4 | include_context 'Rails app pg database', 'spec/fixtures/rails6_users_app' do 5 | let(:env) { {} } 6 | 7 | let(:command_output) do 8 | app.prepare_db 9 | stdout, stderr, = app.capture_cmd(%{./bin/rails r "puts AppMap.instance_variable_get('@configuration').nil?"}, env) 10 | stdout.strip 11 | end 12 | 13 | describe 'with APPMAP=false' do 14 | let(:env) { { 'APPMAP' => 'false' } } 15 | it 'is disabled' do 16 | expect(command_output).to eq('true') 17 | end 18 | end 19 | describe 'without APPMAP=false' do 20 | let(:env) { {} } 21 | it 'is enabled' do 22 | expect(command_output).to eq('false') 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/class_map_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'AppMap::ClassMap' do 6 | describe '.build_from_methods' do 7 | it 'includes method labels' do 8 | map = AppMap.class_map([ruby_method((method :test_method))]) 9 | function = dig_map(map, 5)[0] 10 | expect(function).to include(:labels) 11 | end 12 | 13 | # test method comment 14 | # @labels test.method 15 | def test_method 16 | 'test method body' 17 | end 18 | 19 | def ruby_method(method) 20 | AppMap::Trace::RubyMethod.new AppMap::Config::Package.new('pkg'), method.receiver.class.name, method, false 21 | end 22 | 23 | def dig_map(map, depth) 24 | return map if depth == 0 25 | 26 | dig_map map[0][:children], depth - 1 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/fixtures/rack_users_app/lib/app.rb: -------------------------------------------------------------------------------- 1 | require 'rack/app' 2 | require 'json' 3 | 4 | class App < Rack::App 5 | USERS = Hash.new 6 | 7 | class CodedError < StandardError 8 | attr_reader :code 9 | 10 | def initialize(code) 11 | @code = code 12 | end 13 | end 14 | 15 | get '/health' do 16 | response.status = 204 17 | end 18 | 19 | post '/users' do 20 | params = JSON.parse(request.body.read) 21 | user_id = params['login'] 22 | password = params['password'] 23 | halt 422 unless user_id && password 24 | USERS[user_id] = params 25 | response.headers['location'] = "/users/#{user_id}" 26 | response.status = 201 27 | end 28 | 29 | def halt(code) 30 | raise CodedError, code 31 | end 32 | 33 | error CodedError do |ex| 34 | response.status = ex.code 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's 6 | * vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /spec/method_hash_key_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'appmap/hook/method' 3 | 4 | describe 'method_hash_key' do 5 | describe 'of a normal method' do 6 | it 'is a hash' do 7 | expect(AppMap::Hook.method_hash_key(AppMap::Hook, AppMap::Hook.method(:method_hash_key))).to be_a Integer 8 | end 9 | end 10 | describe 'when the class hash raises an error' do 11 | it 'is nil' do 12 | cls = Class.new do 13 | def say_hello; 'hello'; end 14 | 15 | class << self 16 | def hash 17 | raise TypeError, 'raise the type error needed by the test' 18 | end 19 | end 20 | end 21 | 22 | expect { cls.hash }.to raise_error(TypeError, 'raise the type error needed by the test') 23 | expect(AppMap::Hook.method_hash_key(cls, cls.new.method(:say_hello))).to be_nil 24 | end 25 | end 26 | end -------------------------------------------------------------------------------- /lib/appmap/builtin_hooks/ruby.yml: -------------------------------------------------------------------------------- 1 | - methods: 2 | - Marshal#load 3 | - Marshal#restore 4 | require_name: ruby 5 | handler_class: AppMap::Handler::MarshalLoadHandler 6 | label: deserialize.unsafe 7 | - method: Marshal#dump 8 | require_name: ruby 9 | label: serialize 10 | - method: Array#pack 11 | require_name: ruby 12 | label: array.pack 13 | - methods: 14 | - String#unpack 15 | - String#unpack1 16 | require_name: ruby 17 | label: string.unpack 18 | - methods: 19 | - Kernel#eval 20 | require_name: ruby 21 | label: lang.eval 22 | handler_class: AppMap::Handler::EvalHandler 23 | - methods: 24 | - IO#popen 25 | - Kernel#exec 26 | - Kernel#spawn 27 | - Kernel#syscall 28 | - Kernel#system 29 | - Process#exec 30 | - Process#spawn 31 | require_name: ruby 32 | label: system.exec 33 | - methods: 34 | - ERB#result 35 | require_name: ruby 36 | -------------------------------------------------------------------------------- /lib/appmap/command/agent_setup/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'yaml' 4 | require 'appmap/service/guesser' 5 | 6 | module AppMap 7 | module Command 8 | module AgentSetup 9 | ConfigStruct = Struct.new(:config_file, :overwrite) 10 | 11 | class Config < ConfigStruct 12 | class FileExistsError < StandardError; end 13 | 14 | def perform 15 | raise FileExistsError unless overwrite || !File.exist?(config_file) 16 | 17 | config = { 18 | 'name' => Service::Guesser.guess_name, 19 | 'packages' => Service::Guesser.guess_paths.map { |path| { 'path' => path } }, 20 | 'language' => 'ruby', 21 | 'appmap_dir' => 'tmp/appmap' 22 | } 23 | 24 | File.write(config_file, YAML.dump(config)) 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | 16 | # Ignore pidfiles, but keep the directory. 17 | /tmp/pids/* 18 | !/tmp/pids/ 19 | !/tmp/pids/.keep 20 | 21 | # Ignore uploaded files in development. 22 | /storage/* 23 | !/storage/.keep 24 | /tmp/storage/* 25 | !/tmp/storage/ 26 | !/tmp/storage/.keep 27 | 28 | /public/assets 29 | 30 | # Ignore master key for decrypting credentials and more. 31 | /config/master.key 32 | -------------------------------------------------------------------------------- /lib/appmap/command/agent_setup/init.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'json' 4 | require 'yaml' 5 | require 'appmap/service/guesser' 6 | 7 | module AppMap 8 | module Command 9 | module AgentSetup 10 | InitStruct = Struct.new(:config_file) 11 | 12 | class Init < InitStruct 13 | def perform 14 | config = { 15 | 'name' => Service::Guesser.guess_name, 16 | 'packages' => Service::Guesser.guess_paths.map { |path| { 'path' => path } }, 17 | 'language' => 'ruby', 18 | 'appmap_dir' => 'tmp/appmap' 19 | } 20 | 21 | result = { 22 | configuration: { 23 | filename: config_file, 24 | contents: YAML.dump(config) 25 | } 26 | } 27 | 28 | puts JSON.pretty_generate(result) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/appmap/command/agent_setup/validate.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'json' 4 | require 'appmap/service/validator/config_validator' 5 | 6 | module AppMap 7 | module Command 8 | module AgentSetup 9 | ValidateStruct = Struct.new(:config_file) 10 | 11 | class Validate < ValidateStruct 12 | def perform 13 | schema_path = File.expand_path('../../../../../config-schema.yml', __FILE__) 14 | schema = YAML.safe_load(File.read(schema_path)) 15 | result = { 16 | version: 2, 17 | errors: config_validator.violations.map(&:to_h), 18 | schema: schema 19 | } 20 | puts JSON.pretty_generate(result) 21 | end 22 | 23 | private 24 | 25 | def config_validator 26 | @validator ||= Service::Validator::ConfigValidator.new(config_file) 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/gli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'gli' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("gli", "gli") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/gli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'gli' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("gli", "gli") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rake' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rake", "rake") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/thor: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'thor' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("thor", "thor") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rake' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rake", "rake") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/thor: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'thor' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("thor", "thor") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rake' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rake", "rake") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/rackup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rackup' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rack", "rackup") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/app/controllers/api/users_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | class UsersController < ActionController::API 3 | def index 4 | @users = User.all 5 | render json: @users 6 | end 7 | 8 | def create 9 | params = self.params.key?(:user) ? self.params[:user] : self.params 10 | @user = build_user(params.slice(:login).to_unsafe_h) 11 | unless @user.valid? 12 | error = { 13 | code: 'invalid', 14 | target: 'User', 15 | message: @user.errors.full_messages.join(', '), 16 | details: @user.errors.map { |k, v| { code: 'invalid', target: k, message: Array(v).join(', ') } } 17 | } 18 | return render json: error, status: :unprocessable_entity 19 | end 20 | @user.save 21 | render json: @user, status: :created 22 | end 23 | 24 | def build_user(params) 25 | User.new(params) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/rackup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rackup' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rack", "rackup") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/app/controllers/api/users_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | class UsersController < ActionController::API 3 | def index 4 | @users = User.all 5 | render json: @users 6 | end 7 | 8 | def create 9 | params = self.params.key?(:user) ? self.params[:user] : self.params 10 | @user = build_user(params.slice(:login).to_unsafe_h) 11 | unless @user.valid? 12 | error = { 13 | code: 'invalid', 14 | target: 'User', 15 | message: @user.errors.full_messages.join(', '), 16 | details: @user.errors.map { |k, v| { code: 'invalid', target: k, message: Array(v).join(', ') } } 17 | } 18 | return render json: error, status: :unprocessable_entity 19 | end 20 | @user.save 21 | render json: @user, status: :created 22 | end 23 | 24 | def build_user(params) 25 | User.new(params) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/appmap/builtin_hooks/activesupport.yml: -------------------------------------------------------------------------------- 1 | - method: ActiveSupport::Callbacks::CallbackSequence#invoke_before 2 | label: mvc.before_action 3 | require_name: active_support 4 | force: true 5 | - method: ActiveSupport::Callbacks::CallbackSequence#invoke_after 6 | label: mvc.after_action 7 | require_name: active_support 8 | force: true 9 | - method: ActiveSupport::SecurityUtils#secure_compare 10 | label: crypto.secure_compare 11 | require_name: active_support/security_utils 12 | force: true 13 | - method: ActiveSupport.run_load_hooks 14 | labels: 15 | - deserialize.safe 16 | - lang.eval.safe 17 | require_name: active_support/lazy_load_hooks 18 | force: true 19 | - method: ActiveSupport::MessageEncryptor#encrypt_and_sign 20 | require_name: active_support/message_encryptor 21 | force: true 22 | - method: ActiveSupport::MessageEncryptor#decrypt_and_verify 23 | require_name: active_support/message_encryptor 24 | force: true 25 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/appmap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'appmap' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("appmap", "appmap") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/byebug: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'byebug' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("byebug", "byebug") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/ldiff: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'ldiff' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("diff-lcs", "ldiff") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rspec' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rspec-core", "rspec") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/sequel: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'sequel' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("sequel", "sequel") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/appmap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'appmap' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("appmap", "appmap") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/byebug: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'byebug' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("byebug", "byebug") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/ldiff: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'ldiff' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("diff-lcs", "ldiff") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rspec' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rspec-core", "rspec") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/sequel: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'sequel' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("sequel", "sequel") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rspec' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rspec-core", "rspec") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | gem 'rails', '~> 5' 5 | 6 | gem 'haml-rails' 7 | 8 | gem 'activerecord', require: false 9 | gem 'pg' 10 | gem 'sequel', '= 5.20.0', require: false 11 | gem 'sequel-rails', require: false 12 | gem 'sequel_secure_password', require: false 13 | 14 | group :development, :test do 15 | gem 'appmap', path: '../../..' 16 | gem 'cucumber-rails', require: false 17 | gem 'rspec-rails' 18 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 19 | gem 'pry-byebug' 20 | end 21 | 22 | group :test do 23 | # Require only one of these. 24 | # 'database_cleaner' requries 'database_cleaner-active_record', so don't require it. 25 | gem 'database_cleaner-active_record', require: false 26 | gem 'database_cleaner-sequel', require: false 27 | end 28 | 29 | group :development do 30 | end 31 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/app/controllers/api/users_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | class UsersController < ActionController::API 3 | def index 4 | @users = User.all 5 | render json: @users 6 | end 7 | 8 | def create 9 | params = self.params.key?(:user) ? self.params[:user] : self.params 10 | 11 | @user = build_user(params.slice(:login).to_unsafe_h) 12 | unless @user.valid? 13 | error = { 14 | code: 'invalid', 15 | target: 'User', 16 | message: @user.errors.full_messages.join(', '), 17 | details: @user.errors.map { |k, v| { code: 'invalid', target: k, message: Array(v).join(', ') } } 18 | } 19 | return render json: error, status: :unprocessable_entity 20 | end 21 | @user.save 22 | render json: @user, status: :created 23 | end 24 | 25 | def build_user(params) 26 | User.new(params) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/htmldiff: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'htmldiff' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("diff-lcs", "htmldiff") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/nokogiri: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'nokogiri' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("nokogiri", "nokogiri") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/htmldiff: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'htmldiff' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("diff-lcs", "htmldiff") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/nokogiri: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'nokogiri' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("nokogiri", "nokogiri") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/ruby-parse: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'ruby-parse' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("parser", "ruby-parse") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/sprockets: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'sprockets' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("sprockets", "sprockets") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/ruby-parse: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'ruby-parse' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("parser", "ruby-parse") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/sprockets: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'sprockets' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("sprockets", "sprockets") 30 | -------------------------------------------------------------------------------- /lib/appmap/handler/rails/render_handler.rb: -------------------------------------------------------------------------------- 1 | require 'appmap/handler/function_handler' 2 | 3 | module AppMap 4 | module Handler 5 | module Rails 6 | class RenderHandler < AppMap::Handler::FunctionHandler 7 | def handle_call(receiver, args) 8 | options, _ = args 9 | # TODO: :file, :xml 10 | # https://guides.rubyonrails.org/v5.1/layouts_and_rendering.html 11 | if options[:json] 12 | Thread.current[TEMPLATE_RENDER_FORMAT] = :json 13 | end 14 | 15 | super 16 | end 17 | 18 | def handle_return(call_event_id, elapsed, return_value, exception) 19 | if Thread.current[TEMPLATE_RENDER_FORMAT] == :json 20 | Thread.current[TEMPLATE_RENDER_VALUE] = JSON.parse(return_value) rescue nil 21 | end 22 | Thread.current[TEMPLATE_RENDER_FORMAT] = nil 23 | 24 | super 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/bin/ruby-rewrite: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'ruby-rewrite' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("parser", "ruby-rewrite") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/bin/ruby-rewrite: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'ruby-rewrite' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("parser", "ruby-rewrite") 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | vendor 10 | 11 | # Ignore all logfiles and tempfiles. 12 | /log/* 13 | /tmp/* 14 | !/log/.keep 15 | !/tmp/.keep 16 | 17 | # Ignore uploaded files in development 18 | /storage/* 19 | !/storage/.keep 20 | 21 | /node_modules 22 | /yarn-error.log 23 | 24 | /public/assets 25 | .byebug_history 26 | 27 | # Ignore master key for decrypting credentials and more. 28 | /config/master.key 29 | 30 | # Other 31 | .DS_Store 32 | .appmap 33 | appmap.json 34 | appmap-recording-*.json 35 | capybara-*.html 36 | 37 | # appmap gem is pre-installed, don't lock to a version 38 | Gemfile.lock 39 | 40 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | vendor 10 | 11 | # Ignore all logfiles and tempfiles. 12 | /log/* 13 | /tmp/* 14 | !/log/.keep 15 | !/tmp/.keep 16 | 17 | # Ignore uploaded files in development 18 | /storage/* 19 | !/storage/.keep 20 | 21 | /node_modules 22 | /yarn-error.log 23 | 24 | /public/assets 25 | .byebug_history 26 | 27 | # Ignore master key for decrypting credentials and more. 28 | /config/master.key 29 | 30 | # Other 31 | .DS_Store 32 | .appmap 33 | appmap.json 34 | appmap-recording-*.json 35 | capybara-*.html 36 | 37 | # appmap gem is pre-installed, don't lock to a version 38 | Gemfile.lock 39 | 40 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | gem 'rails', '~> 6' 5 | 6 | gem 'haml-rails' 7 | 8 | gem 'activerecord', require: false 9 | gem 'pg' 10 | gem "puma", "~> 6.4" 11 | gem 'sequel', '>= 5.43.0', require: false 12 | gem 'sequel-rails', require: false 13 | gem 'sequel_secure_password', require: false 14 | 15 | group :development, :test do 16 | gem 'appmap', path: '../../..' 17 | gem 'cucumber-rails', require: false 18 | gem 'rspec-rails' 19 | gem "rswag-specs", "~> 2.8.0" # RSwag - Swagger-based DSL for rspec & accompanying rake task for generating Swagger files 20 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 21 | gem 'pry-byebug' 22 | end 23 | 24 | group :test do 25 | gem 'database_cleaner-active_record', require: false 26 | gem 'database_cleaner-sequel', require: false 27 | end 28 | 29 | group :development do 30 | end 31 | -------------------------------------------------------------------------------- /README_CI.md: -------------------------------------------------------------------------------- 1 | # Configuration variables: 2 | 3 | * `GH_TOKEN`: used by `semantic-release` to push changes to Github and manage releases 4 | * `GEM_HOST_API_KEY`: rubygems API key 5 | * `GEM_ALTERNATIVE_NAME` (optional): used for testing of CI flows, 6 | to avoid publication of test releases under official package name 7 | 8 | # Release command 9 | 10 | `./release.sh` 11 | 12 | Bash wrapper script is used merely as a launcher of `semantic-release` 13 | with extra logic to explicitly determine git url from `TRAVIS_REPO_SLUG` \ 14 | variable if its defined (otherwise git url is taken from `package.json`, 15 | which breaks CI on forked repos). 16 | 17 | # CI flow 18 | 19 | 1. Test happens using current version number specified in `lib/appmap/version.rb`, then `release.sh` launches `semantic-release` to do the rest 20 | 2. The version number is increased (including modicication of `version.rb`) 21 | 3. Gem is published under new version number 22 | 4. Github release is created with the new version number 23 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/spec/requests/rswag_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'rack/test' 3 | require "swagger_helper" 4 | 5 | # rubocop:disable RSpec/EmptyExampleGroup 6 | 7 | RSpec.describe Api::UsersController, type: :request do 8 | describe 'POST /api/users' do 9 | path "/api/users" do 10 | post "new user" do 11 | 12 | describe 'with rswag' do 13 | # demonstrate bug with Rswag::Specs::ExampleGroupHelpers::run_test! 14 | # 15 | # NoMethodError: 16 | # undefined method `metadata' for nil:NilClass 17 | # 18 | # /home/test/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/rswag-specs-2.8.0/lib/rswag/specs/example_group_helpers.rb:135:in `block in run_test!' 19 | # /home/test/src/appmap-ruby/lib/appmap/rspec.rb:241:in `instance_exec' 20 | response "422", "Unprocessable entity" do 21 | run_test! 22 | end 23 | end 24 | 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/appmap/depends/util.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | module AppMap 4 | module Depends 5 | module Util 6 | extend self 7 | 8 | def normalize_path(path, pwd: Dir.pwd) 9 | normalize_path_fn(pwd).(path) 10 | end 11 | 12 | def normalize_paths(paths, pwd: Dir.pwd) 13 | paths.map(&normalize_path_fn(pwd)) 14 | end 15 | 16 | def delete_appmap(appmap_path) 17 | FileUtils.rm_rf(appmap_path) 18 | appmap_file_path = [ appmap_path, 'appmap.json' ].join('.') 19 | File.unlink(appmap_file_path) if File.exist?(appmap_file_path) 20 | rescue 21 | warn "Unable to delete AppMap: #{$!}" 22 | end 23 | 24 | private 25 | 26 | def normalize_path_fn(pwd) 27 | lambda do |path| 28 | next path if AppMap::Util.blank?(path) 29 | 30 | path = path[pwd.length + 1..-1] if path.index(pwd) == 0 31 | path.split(':')[0] 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/fixtures/openssl_recorder/lib/openssl_encrypt.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # From the manual page https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL.html 4 | 5 | require 'openssl' 6 | 7 | module Example 8 | def Example.encrypt 9 | cipher = OpenSSL::Cipher.new 'AES-256-CBC' 10 | cipher.encrypt 11 | iv = cipher.random_iv 12 | 13 | pwd = 'some hopefully not to easily guessable password' 14 | salt = OpenSSL::Random.random_bytes 16 15 | iter = 20000 16 | key_len = cipher.key_len 17 | digest = OpenSSL::Digest::SHA256.new 18 | 19 | key = OpenSSL::PKCS5.pbkdf2_hmac(pwd, salt, iter, key_len, digest) 20 | cipher.key = key 21 | 22 | document = 'the document' 23 | 24 | encrypted = cipher.update document 25 | encrypted << cipher.final 26 | encrypted 27 | end 28 | end 29 | 30 | if __FILE__ == $0 31 | ciphertext = Example.encrypt 32 | require 'base64' 33 | puts "Computed ciphertext #{Base64.urlsafe_encode64(ciphertext)}" 34 | end 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | dist: focal 3 | cache: 4 | - bundler 5 | - yarn 6 | 7 | # Travis pre-installs some Ruby versions. You can see the current list 8 | # by clicking on "Build system information" on line 7 which reveals a 9 | # section called "Pre-installed Ruby versions". Use versions from 10 | # that list to not install rvm and ruby each time. 11 | rvm: 12 | - 3.0.1 # doesn't show in pre-installed list 13 | - 3.1.2 14 | - 3.2.0 15 | - 3.3.1 16 | 17 | addons: 18 | postgresql: "14" 19 | apt: 20 | packages: 21 | - postgresql-14 22 | - postgresql-client-14 23 | env: 24 | global: 25 | - PGPORT=5433 26 | - PGUSER=travis 27 | 28 | before_deploy: 29 | - nvm install lts/* 30 | - | 31 | npm i -g \ 32 | semantic-release \ 33 | @semantic-release/git \ 34 | @semantic-release/changelog \ 35 | semantic-release-rubygem 36 | 37 | deploy: 38 | - provider: script 39 | script: ./release.sh 40 | on: 41 | branch: master 42 | condition: "$TRAVIS_RUBY_VERSION =~ ^3.0" 43 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # "true": "foo" 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /lib/appmap/service/config_analyzer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'appmap/service/validator/config_validator' 4 | 5 | module AppMap 6 | module Service 7 | class ConfigAnalyzer 8 | attr_reader :config_error 9 | 10 | def initialize(config_file) 11 | @config_file = config_file 12 | end 13 | 14 | def app_name 15 | config_validator.config.to_h['name'] if present? 16 | end 17 | 18 | def present? 19 | File.exist?(@config_file) 20 | end 21 | 22 | def valid? 23 | config_validator.valid? 24 | end 25 | 26 | def errors 27 | valid? 28 | config_validator.violations.filter(&:error?).map(&:message) 29 | end 30 | 31 | def warnings 32 | config_validator.violations.filter(&:warning?).map(&:message) 33 | end 34 | 35 | private 36 | 37 | def config_validator 38 | @validator ||= AppMap::Service::Validator::ConfigValidator.new(@config_file) 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/fixtures/rails5_users_app/spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe User do 4 | # TODO: appmap/rspec doesn't handle shared_examples_for 100% correctly yet. 5 | # In my tests, only one of these two tests will be emitted as an appmap. 6 | shared_examples_for 'creates the user' do |username| 7 | let(:login) { username } 8 | let(:user) { User.new(login: login) } 9 | it "creates #{username.inspect}" do 10 | expect(user.save(raise_on_failure: true)).to be_truthy 11 | end 12 | end 13 | 14 | describe 'creation' do 15 | # So, instead of shared_examples_for, let's go with a simple method 16 | # containing the assertions. The method can be called from within an example. 17 | def save_and_verify 18 | expect(user.save(raise_on_failure: true)).to be_truthy 19 | end 20 | 21 | context do 22 | let(:login) { 'charles' } 23 | let(:user) { User.new(login: login) } 24 | it "creates 'charles'" do 25 | save_and_verify 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails6_users_app/spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe User do 4 | # TODO: appmap/rspec doesn't handle shared_examples_for 100% correctly yet. 5 | # In my tests, only one of these two tests will be emitted as an appmap. 6 | shared_examples_for 'creates the user' do |username| 7 | let(:login) { username } 8 | let(:user) { User.new(login: login) } 9 | it "creates #{username.inspect}" do 10 | expect(user.save(raise_on_failure: true)).to be_truthy 11 | end 12 | end 13 | 14 | describe 'creation' do 15 | # So, instead of shared_examples_for, let's go with a simple method 16 | # containing the assertions. The method can be called from within an example. 17 | def save_and_verify 18 | expect(user.save(raise_on_failure: true)).to be_truthy 19 | end 20 | 21 | context do 22 | let(:login) { 'charles' } 23 | let(:user) { User.new(login: login) } 24 | it "creates 'charles'" do 25 | save_and_verify 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | 5 | gem "rails", "~> 7.0.2", ">= 7.0.2.3" 6 | gem 'sprockets-rails' 7 | gem 'haml-rails' 8 | gem "pg", "~> 1.1" 9 | gem 'activerecord', require: false 10 | gem 'sequel', '>= 5.43.0', require: false 11 | gem 'sequel-rails', require: false 12 | gem 'sequel_secure_password', require: false 13 | gem "puma", "~> 5.0" 14 | 15 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 16 | gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] 17 | 18 | group :development do 19 | gem "web-console" 20 | end 21 | 22 | group :test do 23 | gem 'database_cleaner-active_record', require: false 24 | gem 'database_cleaner-sequel', require: false 25 | gem 'rswag' 26 | end 27 | 28 | group :development, :test do 29 | gem 'appmap', path: '../../..' 30 | gem 'pry-byebug', '>=0', '< 99' 31 | gem 'rspec-rails' 32 | gem 'rswag-specs' 33 | end 34 | 35 | gem 'capybara', '~> 3.39', group: :test 36 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe User do 4 | # TODO: appmap/rspec doesn't handle shared_examples_for 100% correctly yet. 5 | # In my tests, only one of these two tests will be emitted as an appmap. 6 | shared_examples_for 'creates the user' do |username| 7 | let(:login) { username } 8 | let(:user) { User.new(login: login) } 9 | it "creates #{username.inspect}" do 10 | expect(user.save(raise_on_failure: true)).to be_truthy 11 | end 12 | end 13 | 14 | describe 'creation' do 15 | # So, instead of shared_examples_for, let's go with a simple method 16 | # containing the assertions. The method can be called from within an example. 17 | def save_and_verify 18 | expect(user.save(raise_on_failure: true)).to be_truthy 19 | end 20 | 21 | context do 22 | let(:login) { 'charles' } 23 | let(:user) { User.new(login: login) } 24 | it "creates 'charles'" do 25 | save_and_verify 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/hook_log_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_spec_helper' 2 | 3 | describe 'HookLog' do 4 | include_context 'rails app', 6 5 | 6 | let(:hook_log_file) { 'spec/fixtures/rails6_users_app/appmap_hook.log' } 7 | 8 | before do 9 | FileUtils.rm_f hook_log_file 10 | end 11 | 12 | context 'with APPMAP_LOG_HOOK=true' do 13 | it 'runs creates appmap_hook.log' do 14 | app.prepare_db 15 | app.run_cmd \ 16 | 'bundle exec rake', 17 | 'RAILS_ENV' => 'test', 18 | 'APPMAP_LOG_HOOK' => 'true' 19 | 20 | expect(Dir['spec/fixtures/rails6_users_app/*']).to include(hook_log_file) 21 | end 22 | it 'results can be directed to stderr' do 23 | app.prepare_db 24 | stdout, stderr, = app.capture_cmd \ 25 | 'bundle exec rake', 26 | 'RAILS_ENV' => 'test', 27 | 'APPMAP_LOG_HOOK' => 'true', 28 | 'APPMAP_LOG_HOOK_FILE' => 'stderr' 29 | 30 | expect(Dir['spec/fixtures/rails6_users_app/*']).to_not include(hook_log_file) 31 | expect(stderr.strip).to include('Elapsed time:') 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/agent_setup_init_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | class AgentSetupInitTest < Minitest::Test 7 | CONFIG_FILENAME = '123.yml' 8 | EXPECTED_CONFIG_CONTENT = %(--- 9 | name: appmap-ruby 10 | packages: 11 | - path: lib 12 | language: ruby 13 | appmap_dir: tmp/appmap 14 | ) 15 | 16 | def test_init_when_config_exists 17 | output = `./exe/appmap-agent-init` 18 | assert_equal 0, $CHILD_STATUS.exitstatus 19 | expected = JSON.pretty_generate({ 20 | configuration: { 21 | filename: 'appmap.yml', 22 | contents: EXPECTED_CONFIG_CONTENT 23 | } 24 | }) 25 | assert_equal expected, output.strip 26 | end 27 | 28 | def test_init_with_custom_config_filename 29 | output = `./exe/appmap-agent-init -c #{CONFIG_FILENAME}` 30 | assert_equal 0, $CHILD_STATUS.exitstatus 31 | expected = JSON.pretty_generate({ 32 | configuration: { 33 | filename: CONFIG_FILENAME, 34 | contents: EXPECTED_CONFIG_CONTENT 35 | } 36 | }) 37 | assert_equal expected, output.strip 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /exe/appmap-agent-config: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'optparse' 5 | require 'appmap' 6 | require 'appmap/command/agent_setup/config' 7 | 8 | @options = { config_file: AppMap::DEFAULT_CONFIG_FILE_PATH, force: false } 9 | 10 | OptionParser.new do |parser| 11 | parser.banner = 'Usage: appmap-agent-config [options]' 12 | 13 | description = "AppMap configuration file path (default: #{AppMap::DEFAULT_CONFIG_FILE_PATH})" 14 | parser.on('-c', '--config=FILEPATH', description) do |filepath| 15 | @options[:config_file] = filepath 16 | end 17 | 18 | parser.on('-f', '--force', 'Overwrite existing configuration file') do 19 | @options[:force] = true 20 | end 21 | end.parse! 22 | 23 | begin 24 | AppMap::Command::AgentSetup::Config.new(@options[:config_file], @options[:force]).perform 25 | 26 | puts "AppMap configuration file created at #{@options[:config_file]}" 27 | rescue AppMap::Command::AgentSetup::Config::FileExistsError 28 | puts "AppMap configuration file already exists at #{@options[:config_file]}" 29 | puts 'Use the --force option to overwrite.' 30 | exit 1 31 | end 32 | -------------------------------------------------------------------------------- /test/record_process_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | require 'English' 6 | 7 | class RecordProcessTest < Minitest::Test 8 | def perform_test(program_name) 9 | Bundler.with_clean_env do 10 | Dir.chdir 'test/fixtures/process_recorder' do 11 | FileUtils.rm_rf 'tmp' 12 | system 'bundle config --local local.appmap ../../..' 13 | system 'bundle' 14 | system(%(bundle exec ruby #{program_name})) 15 | 16 | yield 17 | end 18 | end 19 | end 20 | 21 | def test_hello 22 | perform_test 'hello.rb' do 23 | appmap_file = '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 'process_recorder', metadata['app'] 31 | assert_equal 'record_process', metadata['recorder']['name'] 32 | assert_equal 'ruby', metadata['language']['name'] 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | end 34 | -------------------------------------------------------------------------------- /test/bundle_vendor_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | require 'English' 6 | 7 | class BundleVendorTest < Minitest::Test 8 | def perform_bundle_vendor_app(test_name) 9 | Bundler.with_clean_env do 10 | Dir.chdir 'test/fixtures/bundle_vendor_app' do 11 | FileUtils.rm_rf 'tmp' 12 | FileUtils.mkdir_p 'tmp' 13 | system 'bundle config --local local.appmap ../../..' 14 | system 'bundle' 15 | system(%(bundle exec ruby -Ilib -Itest cli.rb add foobar)) 16 | system({ 'APPMAP' => 'true' }, %(bundle exec ruby -Ilib -Itest cli.rb list)) 17 | 18 | yield 19 | end 20 | end 21 | end 22 | 23 | def test_record_gem 24 | perform_bundle_vendor_app 'parser' do 25 | appmap_file = 'tmp/bundle_vendor_app.appmap.json' 26 | appmap = JSON.parse(File.read(appmap_file)) 27 | assert appmap['classMap'].find { |co| co['name'] == 'gli' } 28 | assert appmap['events'].find do |e| 29 | e['event'] == 'call' && 30 | e['defined_class'] = 'Hacer::Todolist' && 31 | e['method_id'] == 'list' 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/gem_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | require 'English' 6 | require 'json' 7 | 8 | class GemTest < Minitest::Test 9 | def perform_gem_test(test_name) 10 | Bundler.with_clean_env do 11 | Dir.chdir 'test/fixtures/gem_test' do 12 | FileUtils.rm_rf 'tmp' 13 | system 'bundle config --local local.appmap ../../..' 14 | system 'bundle' 15 | system({ 'APPMAP' => 'true' }, %(bundle exec ruby -Ilib -Itest test/#{test_name}_test.rb)) 16 | 17 | yield 18 | end 19 | end 20 | end 21 | 22 | def test_record_gem 23 | perform_gem_test 'parser' do 24 | appmap_file = 'tmp/appmap/minitest/Parser_parser.appmap.json' 25 | appmap = JSON.parse(File.read(appmap_file)) 26 | events = appmap['events'] 27 | assert_equal 6, events.size 28 | assert_equal 'call', events.first['event'] 29 | assert_equal 'default_parser', events.first['method_id'] 30 | assert_match /\lib\/parser\/base\.rb$/, events.first['path'] 31 | assert_equal 'return', events.second['event'] 32 | assert_equal 1, events.second['parent_id'] 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/around_recording_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_spec_helper" 2 | 3 | describe "around recording", order: :defined do 4 | include_context "rails app", "7" 5 | 6 | unless testing_ruby_2? 7 | it "creates a recording of a method labeled job.perform" do 8 | FileUtils.rm_rf "spec/fixtures/rails7_users_app/tmp/appmap/requests" 9 | 10 | app.prepare_db 11 | stdout, stderr, exit_code = app.capture_cmd \ 12 | "bundle exec rake count_users", 13 | "RAILS_ENV" => "development" 14 | 15 | warn stderr if exit_code != 0 16 | warn stdout if exit_code != 0 17 | 18 | user_count = stdout.strip 19 | expect(user_count).to eq("User count: 0") 20 | appmaps_files = Dir.glob("spec/fixtures/rails7_users_app/tmp/appmap/requests/**/*.appmap.json") 21 | expect(appmaps_files.count).to eq(1) 22 | appmap_file = appmaps_files.first 23 | expect(appmap_file).to match(/\d+_perform_now_\d+.appmap\.json\z/) 24 | appmap = JSON.parse(File.read(appmap_file)) 25 | metadata = appmap["metadata"] 26 | expect(metadata["recorder"]).to eq( 27 | "name" => "command", 28 | "type" => "requests" 29 | ) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/appmap/swagger/stable.rb: -------------------------------------------------------------------------------- 1 | module AppMap 2 | module Swagger 3 | # Transform raw Swagger into a "stable" variant. For example, remove descriptions 4 | # and parameter examples, whose variance does not substantially affect the API. 5 | class Stable 6 | def initialize(swagger_yaml) 7 | @swagger_yaml = swagger_yaml 8 | end 9 | 10 | def perform 11 | clean_only = nil 12 | clean = lambda do |obj, properties = %w[description example]| 13 | next obj.each(&clean_only.(properties)) if obj.is_a?(Array) 14 | next unless obj.is_a?(Hash) 15 | 16 | properties.each { |property| obj.delete property } 17 | 18 | obj.each do |key, value| 19 | # Don't clean 'description' from within 'properties' 20 | props = key == 'properties' ? %w[example] : properties 21 | clean_only.(props).(value) 22 | end 23 | 24 | obj 25 | end 26 | 27 | clean_only = lambda do |properties| 28 | lambda do |example| 29 | clean.(example, properties) 30 | end 31 | end 32 | 33 | clean.(@swagger_yaml.deep_dup) 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/expectations/openssl_test_key_sign2-3.1.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "event": "call", 5 | "defined_class": "Example", 6 | "method_id": "sign", 7 | "path": "lib/openssl_key_sign.rb", 8 | "lineno": 10, 9 | "static": true, 10 | "parameters": [ 11 | 12 | ], 13 | "receiver": { 14 | "class": "Module" 15 | } 16 | }, 17 | { 18 | "id": 2, 19 | "event": "call", 20 | "defined_class": "OpenSSL::PKey::PKey", 21 | "method_id": "sign", 22 | "path": "OpenSSL::PKey::PKey#sign", 23 | "static": false, 24 | "parameters": [ 25 | { 26 | "name": "arg", 27 | "class": "Array", 28 | "value": "[e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, the document]", 29 | "kind": "rest", 30 | "size": 2 31 | } 32 | ], 33 | "receiver": { 34 | "class": "OpenSSL::PKey::RSA" 35 | } 36 | }, 37 | { 38 | "id": 3, 39 | "event": "return", 40 | "parent_id": 2, 41 | "return_value": { 42 | "class": "String" 43 | } 44 | }, 45 | { 46 | "id": 4, 47 | "event": "return", 48 | "parent_id": 1, 49 | "return_value": { 50 | "class": "String" 51 | } 52 | } 53 | ] 54 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'net/http' 3 | require 'json' 4 | require 'yaml' 5 | require 'English' 6 | require 'byebug' 7 | require 'webdrivers/chromedriver' 8 | 9 | # Disable default initialization of AppMap 10 | ENV['APPMAP_INITIALIZE'] = 'false' 11 | ENV.delete('RAILS_ENV') 12 | ENV.delete('APP_ENV') 13 | 14 | require 'appmap' 15 | 16 | RSpec.configure do |config| 17 | config.example_status_persistence_file_path = "tmp/rspec_failed_examples.txt" 18 | config.profile_examples = true 19 | end 20 | 21 | # Re-run the Rails specs without re-generating the data. This is useful for efficiently enhancing and 22 | # debugging the test itself. 23 | def use_existing_data? 24 | ENV['USE_EXISTING_DATA'] == 'true' 25 | end 26 | 27 | def ruby_2? 28 | RUBY_VERSION.split('.')[0].to_i == 2 29 | end 30 | 31 | def ruby_3_2_or_higher? 32 | version = RUBY_VERSION.split('.') 33 | ((version[0].to_i == 3 && version[1].to_i >= 2) or 34 | version[0].to_i >= 4) 35 | end 36 | 37 | shared_context 'collect events' do 38 | def collect_events(tracer) 39 | [].tap do |events| 40 | while tracer.event? 41 | events << tracer.next_event.to_h 42 | end 43 | end.map(&AppMap::Util.method(:sanitize_event)) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/appmap/swagger/markdown_descriptions.rb: -------------------------------------------------------------------------------- 1 | require 'rdoc' 2 | require 'reverse_markdown' 3 | 4 | module AppMap 5 | module Swagger 6 | # Transform description fields into Markdown. 7 | class MarkdownDescriptions 8 | def initialize(swagger_yaml) 9 | @swagger_yaml = swagger_yaml 10 | end 11 | 12 | def converter 13 | method(:rdoc_to_markdown) 14 | end 15 | 16 | def perform 17 | to_markdown = lambda do |obj| 18 | next obj.each(&to_markdown) if obj.is_a?(Array) 19 | next unless obj.is_a?(Hash) 20 | 21 | description = obj['description'] 22 | obj['description'] = converter.(description) if description 23 | 24 | obj.reject { |k,v| k == 'properties' }.each_value(&to_markdown) 25 | 26 | obj 27 | end 28 | 29 | to_markdown.(Util.deep_dup(@swagger_yaml)) 30 | end 31 | 32 | protected 33 | 34 | def rdoc_to_markdown(comment) 35 | # Strip tags 36 | comment = comment.split("\n").reject { |line| line =~ /^\s*@/ }.join("\n") 37 | converter = ::RDoc::Markup::ToHtml.new(::RDoc::Options.new) 38 | html = converter.convert(comment).strip 39 | ::ReverseMarkdown.convert(html).strip 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.configure do 8 | # config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap and inline scripts 20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 21 | # config.content_security_policy_nonce_directives = %w(script-src) 22 | # 23 | # # Report CSP violations to a specified URI. See: 24 | # # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 25 | # # config.content_security_policy_report_only = true 26 | # end 27 | -------------------------------------------------------------------------------- /spec/fixtures/rails7_users_app/config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket-<%= Rails.env %> 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket-<%= Rails.env %> 23 | 24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name-<%= Rails.env %> 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | $: << File.join(__dir__, 'lib') 2 | 3 | require 'rspec/core/rake_task' 4 | require 'rake/testtask' 5 | require 'rdoc/task' 6 | require 'rake/extensiontask' 7 | 8 | desc 'build the native extension' 9 | Rake::ExtensionTask.new("appmap") do |ext| 10 | ext.lib_dir = "lib/appmap" 11 | end 12 | 13 | desc 'Install non-Ruby dependencies' 14 | task :install do 15 | system 'yarn install' or raise 'yarn install failed' 16 | end 17 | 18 | RSpec::Core::RakeTask.new spec: %i[compile install] do |task, args| 19 | task.exclude_pattern = 'spec/fixtures/**/*_spec.rb' 20 | task.rspec_opts = '-f doc' 21 | if args.count > 0 22 | # There doesn't appear to be a value for +pattern+ that will 23 | # cause it to be ignored. Setting it to '' or +nil+ causes an 24 | # empty argument to get passed to rspec, which confuses it. 25 | task.pattern = 'never match this' 26 | task.rspec_opts += [nil, *args].join(' ') 27 | end 28 | end 29 | 30 | Rake::RDocTask.new do |rd| 31 | rd.main = 'README.rdoc' 32 | rd.rdoc_files.include(%w[README.rdoc lib/**/*.rb exe/**/*]) 33 | rd.title = 'AppMap' 34 | end 35 | 36 | Rake::TestTask.new(minitest: :compile) do |t| 37 | t.libs << 'test' 38 | t.libs << 'lib' 39 | t.test_files = FileList['test/*_test.rb'] 40 | end 41 | 42 | task test: %i[spec minitest] 43 | 44 | task default: :test 45 | -------------------------------------------------------------------------------- /lib/appmap/handler/rails/context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'appmap/handler' 4 | 5 | module AppMap 6 | module Handler 7 | module Rails 8 | # Context of a rails request tracking. 9 | # Mostly a utility class to clean up and deduplicate request handler code. 10 | class Context 11 | def initialize(environment = nil) 12 | environment[REQUEST_CONTEXT] = self if environment 13 | @thread = Thread.current 14 | end 15 | 16 | def self.from(environment) 17 | environment[REQUEST_CONTEXT] 18 | end 19 | 20 | def self.create(environment) 21 | return if from environment 22 | 23 | new environment 24 | end 25 | 26 | def self.remove(env) 27 | env[REQUEST_CONTEXT] = nil 28 | end 29 | 30 | def find_template_render_value 31 | @thread[TEMPLATE_RENDER_VALUE].tap do 32 | @thread[TEMPLATE_RENDER_VALUE] = nil 33 | end 34 | end 35 | 36 | # context is set on the rack environment to make sure a single request is only recorded once 37 | # even if ActionDispatch::Executor is entered more than once (as can happen with engines) 38 | REQUEST_CONTEXT = 'appmap.handler.request.context' 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/fixtures/hook/exception_method.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ExceptionMethod 4 | def to_s 5 | 'Exception Method fixture' 6 | end 7 | 8 | def raise_exception 9 | raise 'Exception occurred in raise_exception' 10 | end 11 | end 12 | 13 | # subclass from BasicObject so we don't get #to_s. Requires some 14 | # hackery to implement the other methods normally provided by Object. 15 | class NoToSMethod < BasicObject 16 | def is_a?(*args) 17 | return false 18 | end 19 | 20 | def class 21 | return ::Class 22 | end 23 | 24 | def respond_to?(*args) 25 | return false 26 | end 27 | 28 | def inspect 29 | "NoToSMethod" 30 | end 31 | 32 | def say_hello 33 | "hello" 34 | end 35 | end 36 | 37 | class InspectRaises < NoToSMethod 38 | def inspect 39 | ::Kernel.raise "#to_s missing, #inspect raises" 40 | end 41 | 42 | def say_hello 43 | "hello" 44 | end 45 | end 46 | 47 | class ToSRaises 48 | def to_s 49 | raise "#to_s raises" 50 | end 51 | 52 | def say_hello 53 | "hello" 54 | end 55 | end 56 | 57 | class ExceptionMethod 58 | def raise_illegal_utf8_message 59 | raise "809: unexpected token at 'x\x9C\xED=\x8Bv\xD3ƶ\xBF2\xB8]\xC5\xE9qdI\x96eǫ4\xA4h΅\x84\xE5z\x96\xAA\xD8\xE3\xE3D\xB2\xE4J2\x90E\xF8\xF7\xBB\xF7\xCC\xE81\x92\xE2\x88ā'" 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/appmap/handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/inflector/methods' 4 | 5 | module AppMap 6 | # Specific hook handler classes and general related utilities. 7 | module Handler 8 | TEMPLATE_RENDER_FORMAT = 'appmap.handler.template.return_value_format' 9 | TEMPLATE_RENDER_VALUE = 'appmap.handler.template.return_value' 10 | 11 | # Try to find handler module with a given name. 12 | # 13 | # If the module is not loaded, tries to require the appropriate file 14 | # using the usual conventions, eg. `Acme::Handler::AppMap` will try 15 | # to require `acme/handler/app_map`, then `acme/handler` and 16 | # finally `acme`. Raises NameError if the module could not be loaded 17 | # this way. 18 | def self.find(name) 19 | begin 20 | return Object.const_get name 21 | rescue NameError 22 | try_load ActiveSupport::Inflector.underscore name 23 | end 24 | Object.const_get name 25 | end 26 | 27 | def self.try_load(fname) 28 | fname = fname.sub %r{^app_map/}, 'appmap/' 29 | fname = fname.split '/' 30 | until fname.empty? 31 | begin 32 | require fname.join '/' 33 | return 34 | rescue LoadError 35 | # pass 36 | end 37 | fname.pop 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/expectations/openssl_test_key_sign2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "event": "call", 5 | "defined_class": "Example", 6 | "method_id": "sign", 7 | "path": "lib/openssl_key_sign.rb", 8 | "lineno": 10, 9 | "static": true, 10 | "parameters": [ 11 | 12 | ], 13 | "receiver": { 14 | "class": "Module" 15 | } 16 | }, 17 | { 18 | "id": 2, 19 | "event": "call", 20 | "defined_class": "OpenSSL::PKey::PKey", 21 | "method_id": "sign", 22 | "path": "OpenSSL::PKey::PKey#sign", 23 | "static": false, 24 | "parameters": [ 25 | { 26 | "name": "arg", 27 | "class": "OpenSSL::Digest::SHA256", 28 | "value": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", 29 | "kind": "req" 30 | }, 31 | { 32 | "name": "arg", 33 | "class": "String", 34 | "value": "the document", 35 | "kind": "req" 36 | } 37 | ], 38 | "receiver": { 39 | "class": "OpenSSL::PKey::RSA" 40 | } 41 | }, 42 | { 43 | "id": 3, 44 | "event": "return", 45 | "parent_id": 2, 46 | "return_value": { 47 | "class": "String" 48 | } 49 | }, 50 | { 51 | "id": 4, 52 | "event": "return", 53 | "parent_id": 1, 54 | "return_value": { 55 | "class": "String" 56 | } 57 | } 58 | ] 59 | -------------------------------------------------------------------------------- /test/expectations/openssl_test_key_sign1.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "lib", 4 | "type": "package", 5 | "children": [ 6 | { 7 | "name": "Example", 8 | "type": "class", 9 | "children": [ 10 | { 11 | "name": "sign", 12 | "type": "function", 13 | "location": "lib/openssl_key_sign.rb:10", 14 | "static": true 15 | } 16 | ] 17 | } 18 | ] 19 | }, 20 | { 21 | "name": "openssl", 22 | "type": "package", 23 | "children": [ 24 | { 25 | "name": "OpenSSL", 26 | "type": "class", 27 | "children": [ 28 | { 29 | "name": "PKey", 30 | "type": "class", 31 | "children": [ 32 | { 33 | "name": "PKey", 34 | "type": "class", 35 | "children": [ 36 | { 37 | "name": "sign", 38 | "type": "function", 39 | "location": "OpenSSL::PKey::PKey#sign", 40 | "static": false, 41 | "labels": [ 42 | "crypto.pkey" 43 | ] 44 | } 45 | ] 46 | } 47 | ] 48 | } 49 | ] 50 | } 51 | ] 52 | } 53 | ] 54 | -------------------------------------------------------------------------------- /test/fixtures/bundle_vendor_app/cli.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'appmap' 3 | require 'gli' 4 | require 'hacer' 5 | 6 | class App 7 | extend GLI::App 8 | 9 | program_desc 'A simple todo list' 10 | 11 | flag [:t,:tasklist], :default_value => File.join(ENV['HOME'],'.todolist') 12 | 13 | pre do |global_options,command,options,args| 14 | $todo_list = Hacer::Todolist.new(global_options[:tasklist]) 15 | end 16 | 17 | command :add do |c| 18 | c.action do |global_options,options,args| 19 | $todo_list.create(args) 20 | end 21 | end 22 | 23 | command :list do |c| 24 | c.action do 25 | $todo_list.list.each do |todo| 26 | printf("%5d - %s\n",todo.todo_id,todo.text) 27 | end 28 | end 29 | end 30 | 31 | command :done do |c| 32 | c.action do |global_options,options,args| 33 | id = args.shift.to_i 34 | $todo_list.list.each do |todo| 35 | $todo_list.complete(todo) if todo.todo_id == id 36 | end 37 | end 38 | end 39 | end 40 | 41 | exit_status = nil 42 | invoke = -> { exit_status = App.run(ARGV) } 43 | do_appmap = -> { ENV['APPMAP'] == 'true' } 44 | 45 | if do_appmap.() 46 | appmap = AppMap.record do 47 | invoke.() 48 | end 49 | File.write('tmp/bundle_vendor_app.appmap.json', JSON.pretty_generate(appmap)) 50 | else 51 | invoke.() 52 | end 53 | exit exit_status 54 | 55 | -------------------------------------------------------------------------------- /lib/appmap/command/agent_setup/status.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'json' 4 | require 'appmap/service/config_analyzer' 5 | require 'appmap/service/integration_test_path_finder' 6 | require 'appmap/service/test_command_provider' 7 | 8 | module AppMap 9 | module Command 10 | module AgentSetup 11 | StatusStruct = Struct.new(:config_file) 12 | 13 | class Status < StatusStruct 14 | def perform 15 | status = { 16 | test_commands: Service::TestCommandProvider.all, 17 | properties: { 18 | config: { 19 | app: config_analyzer.app_name, 20 | present: config_analyzer.present?, 21 | valid: config_analyzer.valid? 22 | }, 23 | project: { 24 | agentVersion: AppMap::VERSION, 25 | language: 'ruby', 26 | remoteRecordingCapable: Gem.loaded_specs.has_key?('rails'), 27 | integrationTests: Service::IntegrationTestPathFinder.new.count_paths > 0 28 | } 29 | } 30 | } 31 | 32 | puts JSON.pretty_generate(status) 33 | end 34 | 35 | private 36 | 37 | def config_analyzer 38 | @config_analyzer ||= Service::ConfigAnalyzer.new(config_file) 39 | end 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to appmap-ruby 2 | 3 | We are incredibly thankful for the contributions we receive from the community. Before contributing, please take a moment to read our [Contributor License Agreement](https://github.com/applandorg/community/blob/master/docs/CLA%20Instructions.pdf) and our [Code of Conduct](https://github.com/applandorg/community/blob/master/docs/Code%20of%20Conduct%20for%20Contributors.pdf). 4 | 5 | ## Contributor License Agreement 6 | We require our external contributors to sign a Contributor License Agreement ("CLA") in order to ensure that 7 | our projects remain licensed under Free and Open Source licenses such as while allowing 8 | Appland to build a sustainable business. 9 | 10 | AppLand is committed to having a true Free and Open Source Software license for our 11 | non-commercial software. A CLA enables AppLand to safely commercialize our products while 12 | keeping a standard FOSS license with all the rights that license grants to users. 13 | 14 | * [Contributor License Agreement](https://github.com/applandorg/community/blob/master/docs/CLA%20Instructions.pdf) 15 | 16 | 17 | ## Code of Conduct 18 | 19 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and 20 | healthy community. 21 | 22 | * [Code of Conduct](https://github.com/applandorg/community/blob/master/docs/Code%20of%20Conduct%20for%20Contributors.pdf) 23 | -------------------------------------------------------------------------------- /lib/appmap/metadata.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "appmap/util" 4 | 5 | module AppMap 6 | module Metadata 7 | class << self 8 | def detect 9 | { 10 | app: AppMap.configuration.name, 11 | language: { 12 | name: "ruby", 13 | engine: RUBY_ENGINE, 14 | version: RUBY_VERSION 15 | }, 16 | client: { 17 | name: "appmap", 18 | url: AppMap::URL, 19 | version: AppMap::VERSION 20 | } 21 | }.tap do |m| 22 | if defined?(::Rails) && defined?(::Rails.version) 23 | m[:frameworks] ||= [] 24 | m[:frameworks] << { 25 | name: "rails", 26 | version: ::Rails.version 27 | } 28 | end 29 | m[:git] = git_metadata if git_available 30 | end 31 | end 32 | 33 | protected 34 | 35 | def git_available 36 | @git_available = system("git status 2>&1 > /dev/null") if @git_available.nil? 37 | end 38 | 39 | def git_metadata 40 | git_repo = `git config --get remote.origin.url`.strip 41 | git_branch = `git rev-parse --abbrev-ref HEAD`.strip 42 | git_sha = `git rev-parse HEAD`.strip 43 | 44 | { 45 | repository: git_repo, 46 | branch: git_branch, 47 | commit: git_sha 48 | } 49 | end 50 | end 51 | end 52 | end 53 | --------------------------------------------------------------------------------