From 72d2aed98f8d9706a1807bd7d2f64b3eb5d80177 Mon Sep 17 00:00:00 2001 From: Kevin Gilpin Date: Mon, 8 May 2023 12:00:07 -0400 Subject: [PATCH] feat: Mirror test folder structure AppMaps recorded from rspec and minitest are written to sub-folders of appmap_dir. The sub-folders mirror the test directories. This behavior can be disabled with APPMAP_MIRROR_TEST_FOLDERS=false --- lib/appmap/cucumber.rb | 3 ++- lib/appmap/minitest.rb | 2 +- lib/appmap/record.rb | 2 +- lib/appmap/rspec.rb | 2 +- lib/appmap/util.rb | 18 ++++++++++++++++-- spec/rails_recording_spec.rb | 14 +++++++------- spec/record_sql_rails_pg_spec.rb | 2 +- 7 files changed, 29 insertions(+), 14 deletions(-) diff --git a/lib/appmap/cucumber.rb b/lib/appmap/cucumber.rb index fef691e4..d15d4608 100644 --- a/lib/appmap/cucumber.rb +++ b/lib/appmap/cucumber.rb @@ -52,7 +52,7 @@ def write_scenario(scenario, appmap) appmap['metadata'] = update_metadata(scenario, appmap['metadata']) scenario_filename = AppMap::Util.scenario_filename(appmap['metadata']['name']) - AppMap::Util.write_appmap(File.join(APPMAP_OUTPUT_DIR, scenario_filename), appmap) + AppMap::Util.write_appmap(APPMAP_OUTPUT_DIR, scenario_filename, appmap, source_location: scenario.location.to_s) end def enabled? @@ -85,6 +85,7 @@ def update_metadata(scenario, base_metadata) m['name'] = attributes.name m['feature'] = attributes.feature m['feature_group'] = attributes.feature_group + m['source_location'] = scenario.location.to_s m['labels'] ||= [] m['labels'] += (scenario.tags&.map(&:name) || []) m['frameworks'] ||= [] diff --git a/lib/appmap/minitest.rb b/lib/appmap/minitest.rb index a189351e..5e88949d 100644 --- a/lib/appmap/minitest.rb +++ b/lib/appmap/minitest.rb @@ -128,7 +128,7 @@ def save(name:, class_map:, source_location:, test_status:, test_failure:, excep }.compact fname = AppMap::Util.scenario_filename(name) - AppMap::Util.write_appmap(File.join(APPMAP_OUTPUT_DIR, fname), appmap) + AppMap::Util.write_appmap(APPMAP_OUTPUT_DIR, fname, appmap, source_location: source_location) end def enabled? diff --git a/lib/appmap/record.rb b/lib/appmap/record.rb index 12814e05..07358e71 100644 --- a/lib/appmap/record.rb +++ b/lib/appmap/record.rb @@ -24,5 +24,5 @@ 'classMap' => AppMap.class_map(tracer.event_methods), 'events' => events } - AppMap::Util.write_appmap('appmap.json', appmap) + AppMap::Util.write_appmap('.', 'appmap.json', appmap) end diff --git a/lib/appmap/rspec.rb b/lib/appmap/rspec.rb index aebd9b0b..a1de66b3 100644 --- a/lib/appmap/rspec.rb +++ b/lib/appmap/rspec.rb @@ -221,7 +221,7 @@ def save(name:, class_map:, source_location:, test_status:, test_failure:, excep }.compact fname = AppMap::Util.scenario_filename(name) - AppMap::Util.write_appmap(File.join(APPMAP_OUTPUT_DIR, fname), appmap) + AppMap::Util.write_appmap(APPMAP_OUTPUT_DIR, fname, appmap, source_location: source_location) end def enabled? diff --git a/lib/appmap/util.rb b/lib/appmap/util.rb index 7b3a416e..622ee796 100644 --- a/lib/appmap/util.rb +++ b/lib/appmap/util.rb @@ -177,10 +177,23 @@ def swaggerize_path(path) end.join('/') end + def mirror_test_folders? + ENV['APPMAP_MIRROR_TEST_FOLDERS'] != 'false' + end + # Atomically writes AppMap data to +filename+. - def write_appmap(filename, appmap) + def write_appmap(output_dir, filename, appmap, source_location: nil) require 'tmpdir' + path_tokens = [ output_dir ] + if source_location && mirror_test_folders? + source_path, _ = source_location.split(':', 2)[0] + source_dir = Array(Pathname.new(source_path).dirname.each_filename)[1..-1].join('/') + path_tokens.push(source_dir) unless source_dir == '' + end + path_tokens.push(filename) + appmap_path = File.join(*path_tokens) + # This is what Ruby Tempfile does; but we don't want the file to be unlinked. mode = File::RDWR | File::CREAT | File::EXCL ::Dir::Tmpname.create([ 'appmap_', '.json' ]) do |tmpname| @@ -188,7 +201,8 @@ def write_appmap(filename, appmap) tempfile.write(JSON.generate(appmap)) tempfile.close # Atomically move the tempfile into place. - FileUtils.mv tempfile.path, filename + FileUtils.mkdir_p Pathname.new(appmap_path).dirname + FileUtils.mv tempfile.path, appmap_path end end diff --git a/spec/rails_recording_spec.rb b/spec/rails_recording_spec.rb index d0f3184b..f90187c2 100644 --- a/spec/rails_recording_spec.rb +++ b/spec/rails_recording_spec.rb @@ -6,7 +6,7 @@ include_context 'rails integration test setup' describe 'rspec metadata' do - let(:appmap_json_files) { Dir.glob("#{tmpdir}/appmap/rspec/*.appmap.json") } + let(:appmap_json_files) { Dir.glob("#{tmpdir}/appmap/rspec/**/*.appmap.json") } it 'appmap: false disables recording' do test_names = appmap_json_files.map(&File.method(:read)).map(&JSON.method(:parse)).map do |json| @@ -20,7 +20,7 @@ describe 'an API route' do describe 'creating an object' do let(:appmap_json_file) do - 'Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json' + 'controllers/Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json' end it 'http_server_request is recorded in the appmap' do @@ -104,7 +104,7 @@ end context 'with an object-style message' do - let(:appmap_json_file) { 'Api_UsersController_POST_api_users_with_required_parameters_with_object-style_parameters_creates_a_user.appmap.json' } + let(:appmap_json_file) { 'controllers/Api_UsersController_POST_api_users_with_required_parameters_with_object-style_parameters_creates_a_user.appmap.json' } it 'message properties are recorded in the appmap' do expect(events).to include( @@ -126,7 +126,7 @@ describe 'listing objects' do context 'with a custom header' do - let(:appmap_json_file) { 'Api_UsersController_GET_api_users_with_a_custom_header_lists_the_users.appmap.json' } + let(:appmap_json_file) { 'controllers/Api_UsersController_GET_api_users_with_a_custom_header_lists_the_users.appmap.json' } it 'custom header is recorded in the appmap' do expect(events).to include( @@ -144,7 +144,7 @@ describe 'a UI route' do describe 'rendering a page using a template file' do let(:appmap_json_file) do - 'UsersController_GET_users_lists_the_users.appmap.json' + 'controllers/UsersController_GET_users_lists_the_users.appmap.json' end it 'records the template file' do @@ -186,7 +186,7 @@ describe 'rendering a page using a text template' do let(:appmap_json_file) do - 'UsersController_GET_users_login_shows_the_user.appmap.json' + 'controllers/UsersController_GET_users_login_shows_the_user.appmap.json' end it 'records the normalized path info' do @@ -257,7 +257,7 @@ include_context 'rails integration test setup' let(:appmap_json_file) do - 'Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json' + 'controllers/Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json' end it 'http_server_request is recorded' do diff --git a/spec/record_sql_rails_pg_spec.rb b/spec/record_sql_rails_pg_spec.rb index 4aa94ab8..7a43eeb5 100644 --- a/spec/record_sql_rails_pg_spec.rb +++ b/spec/record_sql_rails_pg_spec.rb @@ -49,7 +49,7 @@ def run_specs(orm_module) 'RAILS_ENV' => 'test' end - let(:appmap_json) { File.join tmpdir, "appmap/rspec/#{test_case}.appmap.json" } + let(:appmap_json) { File.join tmpdir, "appmap/rspec/controllers/#{test_case}.appmap.json" } let(:appmap) { JSON.parse(File.read(appmap_json)) } let(:tmpdir) { app.tmpdir } let(:sql_events) { appmap['events'].select { |ev| ev.include? 'sql_query' } }