From 90e7f31698f6d67da00ed3a68a7392127746ced2 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 18 Oct 2022 00:11:06 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- app/helpers/todos_helper.rb | 22 +++--- app/models/environment.rb | 2 +- app/services/releases/create_service.rb | 8 +-- app/services/releases/destroy_service.rb | 4 +- app/services/releases/update_service.rb | 10 +-- app/views/shared/_clone_panel.html.haml | 2 +- .../ci_limit_complete_hierarchy_size.yml | 2 +- doc/administration/gitaly/configure_gitaly.md | 5 +- doc/administration/gitaly/troubleshooting.md | 20 ++++++ doc/update/zero_downtime.md | 4 +- locale/gitlab.pot | 57 +++++++++++++++ qa/Gemfile | 4 +- qa/Gemfile.lock | 18 ++--- rubocop/cop_todo.rb | 14 +++- rubocop/formatter/todo_formatter.rb | 26 +++++-- spec/models/environment_spec.rb | 5 +- spec/rubocop/cop_todo_spec.rb | 35 +++++++++- spec/rubocop/formatter/todo_formatter_spec.rb | 70 +++++++++++++++++-- 18 files changed, 251 insertions(+), 57 deletions(-) diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index f3e23f5324d..520cde9ecee 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -168,22 +168,22 @@ module TodosHelper def todo_actions_options [ - { id: '', text: 'Any Action' }, - { id: Todo::ASSIGNED, text: 'Assigned' }, - { id: Todo::REVIEW_REQUESTED, text: 'Review requested' }, - { id: Todo::MENTIONED, text: 'Mentioned' }, - { id: Todo::MARKED, text: 'Added' }, - { id: Todo::BUILD_FAILED, text: 'Pipelines' } + { id: '', text: s_('Todos|Any Action') }, + { id: Todo::ASSIGNED, text: s_('Todos|Assigned') }, + { id: Todo::REVIEW_REQUESTED, text: s_('Todos|Review requested') }, + { id: Todo::MENTIONED, text: s_('Todos|Mentioned') }, + { id: Todo::MARKED, text: s_('Todos|Added') }, + { id: Todo::BUILD_FAILED, text: s_('Todos|Pipelines') } ] end def todo_types_options [ - { id: '', text: 'Any Type' }, - { id: 'Issue', text: 'Issue' }, - { id: 'MergeRequest', text: 'Merge request' }, - { id: 'DesignManagement::Design', text: 'Design' }, - { id: 'AlertManagement::Alert', text: 'Alert' } + { id: '', text: s_('Todos|Any Type') }, + { id: 'Issue', text: s_('Todos|Issue') }, + { id: 'MergeRequest', text: s_('Todos|Merge request') }, + { id: 'DesignManagement::Design', text: s_('Todos|Design') }, + { id: 'AlertManagement::Alert', text: s_('Todos|Alert') } ] end diff --git a/app/models/environment.rb b/app/models/environment.rb index a601b3a88a8..2d3f342953f 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -544,7 +544,7 @@ class Environment < ApplicationRecord self.class.tiers[:development] when /(test|tst|int|ac(ce|)pt|qa|qc|control|quality)/i self.class.tiers[:testing] - when /(st(a|)g|mod(e|)l|pre|demo)/i + when /(st(a|)g|mod(e|)l|pre|demo|non)/i self.class.tiers[:staging] when /(pr(o|)d|live)/i self.class.tiers[:production] diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb index b7df201824a..01dd6323d94 100644 --- a/app/services/releases/create_service.rb +++ b/app/services/releases/create_service.rb @@ -3,10 +3,10 @@ module Releases class CreateService < Releases::BaseService def execute - return error('Access Denied', 403) unless allowed? - return error('You are not allowed to create this tag as it is protected.', 403) unless can_create_tag? - return error('Release already exists', 409) if release - return error("Milestone(s) not found: #{inexistent_milestones.join(', ')}", 400) if inexistent_milestones.any? + return error(_('Access Denied'), 403) unless allowed? + return error(_('You are not allowed to create this tag as it is protected.'), 403) unless can_create_tag? + return error(_('Release already exists'), 409) if release + return error(format(_("Milestone(s) not found: %{milestones}"), milestones: inexistent_milestones.join(', ')), 400) if inexistent_milestones.any? # rubocop:disable Layout/LineLength # should be found before the creation of new tag # because tag creation can spawn new pipeline diff --git a/app/services/releases/destroy_service.rb b/app/services/releases/destroy_service.rb index 8abf9308689..ff2b3a7bd18 100644 --- a/app/services/releases/destroy_service.rb +++ b/app/services/releases/destroy_service.rb @@ -3,8 +3,8 @@ module Releases class DestroyService < Releases::BaseService def execute - return error('Release does not exist', 404) unless release - return error('Access Denied', 403) unless allowed? + return error(_('Release does not exist'), 404) unless release + return error(_('Access Denied'), 403) unless allowed? if release.destroy success(tag: existing_tag, release: release) diff --git a/app/services/releases/update_service.rb b/app/services/releases/update_service.rb index 2e0a2f8488a..b9b2aba9805 100644 --- a/app/services/releases/update_service.rb +++ b/app/services/releases/update_service.rb @@ -31,11 +31,11 @@ module Releases private def validate - return error('Tag does not exist', 404) unless existing_tag - return error('Release does not exist', 404) unless release - return error('Access Denied', 403) unless allowed? - return error('params is empty', 400) if empty_params? - return error("Milestone(s) not found: #{inexistent_milestones.join(', ')}", 400) if inexistent_milestones.any? + return error(_('Tag does not exist'), 404) unless existing_tag + return error(_('Release does not exist'), 404) unless release + return error(_('Access Denied'), 403) unless allowed? + return error(_('params is empty'), 400) if empty_params? + return error(format(_("Milestone(s) not found: %{milestones}"), milestones: inexistent_milestones.join(', ')), 400) if inexistent_milestones.any? # rubocop:disable Layout/LineLength end def allowed? diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index abaf250fa69..48ae1f7eb1d 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -9,7 +9,7 @@ %span.js-clone-dropdown-label = default_clone_protocol.upcase = sprite_icon('chevron-down', css_class: 'gl-icon') - %ul.dropdown-menu.dropdown-menu-selectable{ data: { qa_selector: 'clone_dropdown_content' } } + %ul.dropdown-menu.dropdown-menu-selectable.clone-options-dropdown{ data: { qa_selector: 'clone_dropdown_content' } } %li = ssh_clone_button(container) %li diff --git a/config/feature_flags/development/ci_limit_complete_hierarchy_size.yml b/config/feature_flags/development/ci_limit_complete_hierarchy_size.yml index ad0dd85a25a..d6cc8787333 100644 --- a/config/feature_flags/development/ci_limit_complete_hierarchy_size.yml +++ b/config/feature_flags/development/ci_limit_complete_hierarchy_size.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/373719 milestone: '15.4' type: development group: group::pipeline execution -default_enabled: false +default_enabled: true diff --git a/doc/administration/gitaly/configure_gitaly.md b/doc/administration/gitaly/configure_gitaly.md index f6bf563b8cd..e4aef2db9a8 100644 --- a/doc/administration/gitaly/configure_gitaly.md +++ b/doc/administration/gitaly/configure_gitaly.md @@ -555,12 +555,15 @@ Additionally, the certificate (or its certificate authority) must be installed o - Gitaly servers. - Gitaly clients that communicate with it. -Note the following: +### Certificate requirements - The certificate must specify the address you use to access the Gitaly server. You must add the hostname or IP address as a Subject Alternative Name to the certificate. - You can configure Gitaly servers with both an unencrypted listening address `listen_addr` and an encrypted listening address `tls_listen_addr` at the same time. This allows you to gradually transition from unencrypted to encrypted traffic if necessary. +- The certificate's Common Name field is ignored. + +### Configure Gitaly with TLS To configure Gitaly with TLS: diff --git a/doc/administration/gitaly/troubleshooting.md b/doc/administration/gitaly/troubleshooting.md index b53534cdceb..1c5f0d43864 100644 --- a/doc/administration/gitaly/troubleshooting.md +++ b/doc/administration/gitaly/troubleshooting.md @@ -67,6 +67,13 @@ remote: GitLab: 401 Unauthorized You need to sync your `gitlab-secrets.json` file with your GitLab application nodes. +### 500 and `fetching folder content` errors on repository pages + +`Fetching folder content`, and in some cases `500`, errors indicate +connectivity problems between GitLab and Gitaly. +Consult the [client-side gRPC logs](#client-side-grpc-logs) +for details. + ### Client side gRPC logs Gitaly uses the [gRPC](https://grpc.io/) RPC framework. The Ruby gRPC @@ -81,6 +88,19 @@ You can run a gRPC trace with: sudo GRPC_TRACE=all GRPC_VERBOSITY=DEBUG gitlab-rake gitlab:gitaly:check ``` +If this command fails with a `failed to connect to all addresses` error, +check for an SSL or TLS problem: + +```shell +/opt/gitlab/embedded/bin/openssl s_client -connect : -verify_return_error +``` + +Check whether `Verify return code` field indicates a +[known Omnibus GitLab configuration problem](https://docs.gitlab.com/omnibus/settings/ssl.html). + +If `openssl` succeeds but `gitlab-rake gitlab:gitaly:check` fails, +check [certificate requirements](configure_gitaly.md#certificate-requirements) for Gitaly. + ### Server side gRPC logs gRPC tracing can also be enabled in Gitaly itself with the `GODEBUG=http2debug` diff --git a/doc/update/zero_downtime.md b/doc/update/zero_downtime.md index 9ed09e17d01..aebd27f84a9 100644 --- a/doc/update/zero_downtime.md +++ b/doc/update/zero_downtime.md @@ -313,7 +313,7 @@ node throughout the process. - If you're using PgBouncer: - You must bypass PgBouncer and connect directly to the database leader + You must [bypass PgBouncer](../administration/postgresql/pgbouncer.md#procedure-for-bypassing-pgbouncer) and connect directly to the database leader before running migrations. Rails uses an advisory lock when attempting to run a migration to prevent @@ -699,7 +699,7 @@ sudo touch /etc/gitlab/skip-auto-reconfigure 1. If you're using PgBouncer: - You must bypass PgBouncer and connect directly to the database leader + You must [bypass PgBouncer](../administration/postgresql/pgbouncer.md#procedure-for-bypassing-pgbouncer) and connect directly to the database leader before running migrations. Rails uses an advisory lock when attempting to run a migration to prevent diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 6e155e9bc7b..a4ac7b42231 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1933,6 +1933,9 @@ msgstr "" msgid "Acceptable for use in this project" msgstr "" +msgid "Access Denied" +msgstr "" + msgid "Access Git repositories or the API." msgstr "" @@ -25994,6 +25997,9 @@ msgstr "" msgid "Milestone lists not available with your current license" msgstr "" +msgid "Milestone(s) not found: %{milestones}" +msgstr "" + msgid "MilestoneCombobox|An error occurred while searching for milestones" msgstr "" @@ -33441,6 +33447,9 @@ msgstr[1] "" msgid "Release %{deletedRelease} has been successfully deleted." msgstr "" +msgid "Release already exists" +msgstr "" + msgid "Release assets" msgstr "" @@ -33450,6 +33459,9 @@ msgstr "" msgid "Release date" msgstr "" +msgid "Release does not exist" +msgstr "" + msgid "Release does not have the same project as the milestone" msgstr "" @@ -39576,6 +39588,9 @@ msgstr "" msgid "Tag" msgstr "" +msgid "Tag does not exist" +msgstr "" + msgid "Tag list:" msgstr "" @@ -42222,9 +42237,30 @@ msgstr "" msgid "Todos count" msgstr "" +msgid "Todos|Added" +msgstr "" + +msgid "Todos|Alert" +msgstr "" + +msgid "Todos|Any Action" +msgstr "" + +msgid "Todos|Any Type" +msgstr "" + msgid "Todos|Are you looking for things to do? Take a look at %{strongStart}%{openIssuesLinkStart}open issues%{openIssuesLinkEnd}%{strongEnd}, contribute to %{strongStart}%{mergeRequestLinkStart}a merge request%{mergeRequestLinkEnd}%{mergeRequestLinkEnd}%{strongEnd}, or mention someone in a comment to automatically assign them a new to-do item." msgstr "" +msgid "Todos|Assigned" +msgstr "" + +msgid "Todos|Design" +msgstr "" + +msgid "Todos|Epic" +msgstr "" + msgid "Todos|Filter by author" msgstr "" @@ -42246,18 +42282,33 @@ msgstr "" msgid "Todos|Isn't an empty To-Do List beautiful?" msgstr "" +msgid "Todos|Issue" +msgstr "" + msgid "Todos|It's how you always know what to work on next." msgstr "" msgid "Todos|Mark all as done" msgstr "" +msgid "Todos|Mentioned" +msgstr "" + +msgid "Todos|Merge request" +msgstr "" + msgid "Todos|Nothing is on your to-do list. Nice work!" msgstr "" msgid "Todos|Nothing left to do. High five!" msgstr "" +msgid "Todos|Pipelines" +msgstr "" + +msgid "Todos|Review requested" +msgstr "" + msgid "Todos|Undo mark all as done" msgstr "" @@ -46020,6 +46071,9 @@ msgstr "" msgid "You are not allowed to approve a user" msgstr "" +msgid "You are not allowed to create this tag as it is protected." +msgstr "" + msgid "You are not allowed to log in using password" msgstr "" @@ -48589,6 +48643,9 @@ msgstr "" msgid "pages" msgstr "" +msgid "params is empty" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "" diff --git a/qa/Gemfile b/qa/Gemfile index 4dd02c7b3d8..61d4b2d5059 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -4,7 +4,7 @@ source 'https://rubygems.org' gem 'gitlab-qa', '~> 8', require: 'gitlab/qa' gem 'activesupport', '~> 6.1.4.7' # This should stay in sync with the root's Gemfile -gem 'allure-rspec', '~> 2.16.0' +gem 'allure-rspec', '~> 2.18.0' gem 'capybara', '~> 3.35.0' gem 'capybara-screenshot', '~> 1.0.26' gem 'rake', '~> 13' @@ -14,7 +14,7 @@ gem 'airborne', '~> 0.3.7', require: false # airborne is messing with rspec sand gem 'rest-client', '~> 2.1.0' gem 'rspec-retry', '~> 0.6.1', require: 'rspec/retry' gem 'rspec_junit_formatter', '~> 0.6.0' -gem 'faker', '~> 2.19', '>= 2.19.0' +gem 'faker', '~> 2.23' gem 'knapsack', '~> 4.0' gem 'parallel_tests', '~> 2.32' gem 'rotp', '~> 6.2.0' diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 280d0a72c29..837e7e8bdb0 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -15,10 +15,10 @@ GEM rack-test (>= 1.1.0, < 2.0) rest-client (>= 2.0.2, < 3.0) rspec (~> 3.8) - allure-rspec (2.16.1) - allure-ruby-commons (= 2.16.1) + allure-rspec (2.18.0) + allure-ruby-commons (= 2.18.0) rspec-core (>= 3.8, < 4) - allure-ruby-commons (2.16.1) + allure-ruby-commons (2.18.0) mime-types (>= 3.3, < 4) oj (>= 3.10, < 4) require_all (>= 2, < 4) @@ -60,8 +60,8 @@ GEM domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) excon (0.92.4) - faker (2.19.0) - i18n (>= 1.6, < 2) + faker (2.23.0) + i18n (>= 1.8.11, < 2) faraday (2.5.2) faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) @@ -182,7 +182,7 @@ GEM octokit (5.6.1) faraday (>= 1, < 3) sawyer (~> 0.9) - oj (3.13.11) + oj (3.13.21) os (1.1.4) parallel (1.19.2) parallel_tests (2.32.0) @@ -299,14 +299,14 @@ PLATFORMS DEPENDENCIES activesupport (~> 6.1.4.7) airborne (~> 0.3.7) - allure-rspec (~> 2.16.0) + allure-rspec (~> 2.18.0) capybara (~> 3.35.0) capybara-screenshot (~> 1.0.26) chemlab (~> 0.10) chemlab-library-www-gitlab-com (~> 0.1) confiner (~> 0.3) deprecation_toolkit (~> 2.0.0) - faker (~> 2.19, >= 2.19.0) + faker (~> 2.23) faraday-retry (~> 2.0) fog-core (= 2.1.0) fog-google (~> 1.19) @@ -336,4 +336,4 @@ DEPENDENCIES zeitwerk (~> 2.4) BUNDLED WITH - 2.3.23 + 2.3.24 diff --git a/rubocop/cop_todo.rb b/rubocop/cop_todo.rb index a36afc08673..943f3375461 100644 --- a/rubocop/cop_todo.rb +++ b/rubocop/cop_todo.rb @@ -26,10 +26,14 @@ module RuboCop @cop_class&.support_autocorrect? end + def generate? + previously_disabled || grace_period || files.any? + end + def to_yaml yaml = [] yaml << '---' - yaml << '# Cop supports --auto-correct.' if autocorrectable? + yaml << '# Cop supports --autocorrect.' if autocorrectable? yaml << "#{cop_name}:" if previously_disabled @@ -39,8 +43,12 @@ module RuboCop end yaml << " #{RuboCop::Formatter::GracefulFormatter.grace_period_key_value}" if grace_period - yaml << ' Exclude:' - yaml.concat files.sort.map { |file| " - '#{file}'" } + + if files.any? + yaml << ' Exclude:' + yaml.concat files.sort.map { |file| " - '#{file}'" } + end + yaml << '' yaml.join("\n") diff --git a/rubocop/formatter/todo_formatter.rb b/rubocop/formatter/todo_formatter.rb index b1c6d1c1688..5e49e2dc082 100644 --- a/rubocop/formatter/todo_formatter.rb +++ b/rubocop/formatter/todo_formatter.rb @@ -31,6 +31,7 @@ module RuboCop @config_inspect_todo_dir = load_config_inspect_todo_dir @config_old_todo_yml = load_config_old_todo_yml check_multiple_configurations! + create_empty_todos(@config_inspect_todo_dir) super end @@ -47,11 +48,9 @@ module RuboCop def finished(_inspected_files) @todos.values.sort_by(&:cop_name).each do |todo| - todo.previously_disabled = previously_disabled?(todo) - todo.grace_period = grace_period?(todo) - validate_todo!(todo) - path = @todo_dir.write(todo.cop_name, todo.to_yaml) + next unless configure_and_validate_todo(todo) + path = @todo_dir.write(todo.cop_name, todo.to_yaml) output.puts "Written to #{relative_path(path)}\n" end end @@ -82,6 +81,14 @@ module RuboCop raise "Multiple configurations found for cops:\n#{list}\n" end + # For each inspected cop TODO config create a TODO object to make sure + # the cop TODO config will be written even without any offenses. + def create_empty_todos(inspected_cop_config) + inspected_cop_config.each_key do |cop_name| + @todos[cop_name] + end + end + def config_for(todo) cop_name = todo.cop_name @@ -101,10 +108,15 @@ module RuboCop GracefulFormatter.grace_period?(todo.cop_name, config) end - def validate_todo!(todo) - return unless todo.previously_disabled && todo.grace_period + def configure_and_validate_todo(todo) + todo.previously_disabled = previously_disabled?(todo) + todo.grace_period = grace_period?(todo) - raise "#{todo.cop_name}: Cop must be enabled to use `#{GracefulFormatter.grace_period_key_value}`." + if todo.previously_disabled && todo.grace_period + raise "#{todo.cop_name}: Cop must be enabled to use `#{GracefulFormatter.grace_period_key_value}`." + end + + todo.generate? end def load_config_inspect_todo_dir diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index a5806910b23..a442856d993 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -390,7 +390,10 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do 'staging' | described_class.tiers[:staging] 'pre-prod' | described_class.tiers[:staging] 'blue-kit-stage' | described_class.tiers[:staging] - 'pre-prod' | described_class.tiers[:staging] + 'nonprod' | described_class.tiers[:staging] + 'nonlive' | described_class.tiers[:staging] + 'non-prod' | described_class.tiers[:staging] + 'non-live' | described_class.tiers[:staging] 'gprd' | described_class.tiers[:production] 'gprd-cny' | described_class.tiers[:production] 'production' | described_class.tiers[:production] diff --git a/spec/rubocop/cop_todo_spec.rb b/spec/rubocop/cop_todo_spec.rb index 3f9c378b303..c641001789f 100644 --- a/spec/rubocop/cop_todo_spec.rb +++ b/spec/rubocop/cop_todo_spec.rb @@ -66,6 +66,38 @@ RSpec.describe RuboCop::CopTodo do end end + describe '#generate?' do + subject { cop_todo.generate? } + + context 'when empty todo' do + it { is_expected.to eq(false) } + end + + context 'when previously disabled' do + before do + cop_todo.previously_disabled = true + end + + it { is_expected.to eq(true) } + end + + context 'when in grace period' do + before do + cop_todo.grace_period = true + end + + it { is_expected.to eq(true) } + end + + context 'with offenses recorded' do + before do + cop_todo.record('a.rb', 1) + end + + it { is_expected.to eq(true) } + end + end + describe '#to_yaml' do subject(:yaml) { cop_todo.to_yaml } @@ -77,9 +109,8 @@ RSpec.describe RuboCop::CopTodo do specify do expect(yaml).to eq(<<~YAML) --- - # Cop supports --auto-correct. + # Cop supports --autocorrect. #{cop_name}: - Exclude: YAML end end diff --git a/spec/rubocop/formatter/todo_formatter_spec.rb b/spec/rubocop/formatter/todo_formatter_spec.rb index edd84632409..5494d518605 100644 --- a/spec/rubocop/formatter/todo_formatter_spec.rb +++ b/spec/rubocop/formatter/todo_formatter_spec.rb @@ -82,7 +82,7 @@ RSpec.describe RuboCop::Formatter::TodoFormatter do expect(todo_yml('B/AutoCorrect')).to eq(<<~YAML) --- - # Cop supports --auto-correct. + # Cop supports --autocorrect. B/AutoCorrect: Exclude: - 'd.rb' @@ -309,18 +309,78 @@ RSpec.describe RuboCop::Formatter::TodoFormatter do context 'without offenses detected' do before do + todo_dir.write('A/Cop', yaml) if yaml + todo_dir.inspect_all + formatter.started(%w[a.rb b.rb]) formatter.file_finished('a.rb', []) formatter.file_finished('b.rb', []) formatter.finished(%w[a.rb b.rb]) + + todo_dir.delete_inspected end - it 'does not output anything' do - expect(stdout.string).to eq('') + context 'without existing TODOs' do + let(:yaml) { nil } + + it 'does not output anything' do + expect(stdout.string).to eq('') + end + + it 'does not write any YAML files' do + expect(rubocop_todo_dir_listing).to be_empty + end end - it 'does not write any YAML files' do - expect(rubocop_todo_dir_listing).to be_empty + context 'with existing TODOs' do + context 'when existing offenses only' do + let(:yaml) do + <<~YAML + --- + A/Cop: + Exclude: + - x.rb + YAML + end + + it 'does not output anything' do + expect(stdout.string).to eq('') + end + + it 'does not write any YAML files' do + expect(rubocop_todo_dir_listing).to be_empty + end + end + + context 'when in grace period' do + let(:yaml) do + <<~YAML + --- + A/Cop: + Details: grace period + Exclude: + - x.rb + YAML + end + + it 'outputs its actions' do + expect(stdout.string).to eq(<<~OUTPUT) + Written to .rubocop_todo/a/cop.yml + OUTPUT + end + + it 'creates YAML file with Details only', :aggregate_failures do + expect(rubocop_todo_dir_listing).to contain_exactly( + 'a/cop.yml' + ) + + expect(todo_yml('A/Cop')).to eq(<<~YAML) + --- + A/Cop: + Details: grace period + YAML + end + end end end