diff options
44 files changed, 314 insertions, 523 deletions
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index 6f33057a057..72f0b5028a5 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -54,6 +54,7 @@ review-deploy: extends: - .review-workflow-base - .review:rules:review-deploy + retry: 2 stage: review needs: ["review-build-cng"] resource_group: "review/${CI_COMMIT_REF_NAME}" diff --git a/app/assets/javascripts/issuable/components/csv_export_modal.vue b/app/assets/javascripts/issuable/components/csv_export_modal.vue index f17440a4a14..5c880cbfad8 100644 --- a/app/assets/javascripts/issuable/components/csv_export_modal.vue +++ b/app/assets/javascripts/issuable/components/csv_export_modal.vue @@ -51,7 +51,6 @@ export default { </template> <div v-if="issuableCount > -1" - data-testid="issuable-count-note" class="gl-justify-content-start gl-align-items-center gl-p-4 gl-border-b-solid gl-border-1 gl-border-gray-50" > <gl-icon name="check" class="gl-color-green-400" /> diff --git a/app/assets/javascripts/issuable/components/csv_import_export_buttons.vue b/app/assets/javascripts/issuable/components/csv_import_export_buttons.vue index fb4d5aca2f5..5d75b87fe76 100644 --- a/app/assets/javascripts/issuable/components/csv_import_export_buttons.vue +++ b/app/assets/javascripts/issuable/components/csv_import_export_buttons.vue @@ -72,9 +72,6 @@ export default { importModalId() { return `${this.issuableType}-import-modal`; }, - importButtonText() { - return this.showLabel ? this.$options.i18n.importIssuesText : null; - }, importButtonTooltipText() { return this.showLabel ? null : this.$options.i18n.importIssuesText; }, @@ -90,29 +87,25 @@ export default { <gl-button-group> <gl-button v-if="showExportButton" - v-gl-tooltip.hover="$options.i18n.exportAsCsvButtonText" + v-gl-tooltip="$options.i18n.exportAsCsvButtonText" v-gl-modal="exportModalId" icon="export" :aria-label="$options.i18n.exportAsCsvButtonText" data-qa-selector="export_as_csv_button" - data-testid="export-csv-button" /> <gl-dropdown v-if="showImportButton" - v-gl-tooltip.hover="importButtonTooltipText" + v-gl-tooltip="importButtonTooltipText" data-qa-selector="import_issues_dropdown" - data-testid="import-csv-dropdown" - :text="importButtonText" + :text="$options.i18n.importIssuesText" + :text-sr-only="!showLabel" :icon="importButtonIcon" > - <gl-dropdown-item v-gl-modal="importModalId" data-testid="import-csv-link">{{ - __('Import CSV') - }}</gl-dropdown-item> + <gl-dropdown-item v-gl-modal="importModalId">{{ __('Import CSV') }}</gl-dropdown-item> <gl-dropdown-item v-if="canEdit" :href="projectImportJiraPath" data-qa-selector="import_from_jira_link" - data-testid="import-from-jira-link" >{{ __('Import from Jira') }}</gl-dropdown-item > </gl-dropdown> diff --git a/app/assets/javascripts/issuable/components/csv_import_modal.vue b/app/assets/javascripts/issuable/components/csv_import_modal.vue index 77fc2f31583..c85efd60b8b 100644 --- a/app/assets/javascripts/issuable/components/csv_import_modal.vue +++ b/app/assets/javascripts/issuable/components/csv_import_modal.vue @@ -48,13 +48,7 @@ export default { <template> <gl-modal :modal-id="modalId" :title="__('Import issues')"> - <form - ref="form" - :action="importCsvIssuesPath" - enctype="multipart/form-data" - method="post" - data-testid="import-csv-form" - > + <form ref="form" :action="importCsvIssuesPath" enctype="multipart/form-data" method="post"> <input :value="$options.csrf.token" type="hidden" name="authenticity_token" /> <p> {{ diff --git a/app/assets/javascripts/issuable/components/issuable_by_email.vue b/app/assets/javascripts/issuable/components/issuable_by_email.vue index d0ce8c2c34b..97807ba1317 100644 --- a/app/assets/javascripts/issuable/components/issuable_by_email.vue +++ b/app/assets/javascripts/issuable/components/issuable_by_email.vue @@ -91,7 +91,7 @@ export default { <template> <div> - <gl-button v-gl-modal="$options.modalId" variant="link" data-testid="issuable-email-modal-btn" + <gl-button v-gl-modal="$options.modalId" variant="link" ><gl-sprintf :message="__('Email a new %{name} to this project')" ><template #name>{{ issuableName }}</template></gl-sprintf ></gl-button @@ -122,7 +122,6 @@ export default { :title="$options.i18n.sendEmail" :aria-label="$options.i18n.sendEmail" icon="mail" - data-testid="mail-to-btn" /> </template> </gl-form-input-group> @@ -156,12 +155,7 @@ export default { /></gl-link> </template> <template #resetLink="{ content }"> - <gl-button - variant="link" - data-testid="incoming-email-token-reset" - @click="resetIncomingEmailToken" - >{{ content }}</gl-button - > + <gl-button variant="link" @click="resetIncomingEmailToken">{{ content }}</gl-button> </template> </gl-sprintf> </p> diff --git a/app/assets/javascripts/issuable/components/status_box.vue b/app/assets/javascripts/issuable/components/status_box.vue index cb768f2bc5b..bd6fdc131cb 100644 --- a/app/assets/javascripts/issuable/components/status_box.vue +++ b/app/assets/javascripts/issuable/components/status_box.vue @@ -91,11 +91,7 @@ export default { <template> <div :class="statusBoxClass" class="issuable-status-box status-box"> - <gl-icon - :name="statusIconName" - class="gl-display-block gl-sm-display-none!" - data-testid="status-icon" - /> + <gl-icon :name="statusIconName" class="gl-display-block gl-sm-display-none!" /> <span class="gl-display-none gl-sm-display-block"> {{ statusHumanName }} </span> diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 5b3aa3cf9dc..a989a1f77da 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -484,13 +484,17 @@ export const setUrlParams = ( searchParams.delete(key); } else if (Array.isArray(params[key])) { const keyName = railsArraySyntax ? `${key}[]` : key; - params[key].forEach((val, idx) => { - if (idx === 0) { - searchParams.set(keyName, val); - } else { - searchParams.append(keyName, val); - } - }); + if (params[key].length === 0) { + searchParams.delete(keyName); + } else { + params[key].forEach((val, idx) => { + if (idx === 0) { + searchParams.set(keyName, val); + } else { + searchParams.append(keyName, val); + } + }); + } } else { searchParams.set(key, params[key]); } diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss index 579a68ac8e4..40e11b50eba 100644 --- a/app/assets/stylesheets/framework/gfm.scss +++ b/app/assets/stylesheets/framework/gfm.scss @@ -4,7 +4,8 @@ .gfm-commit, .gfm-commit_range { - @extend .commit-sha; + @include gl-font-monospace; + font-size: 95%; } .gfm-project_member { diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index cd99c667001..f83ba89daae 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -45,7 +45,6 @@ input[type='checkbox']:hover { margin: 0 8px; form { - @extend .form-control; margin: 0; padding: 4px; width: $search-input-width; @@ -139,7 +138,6 @@ input[type='checkbox']:hover { &.search-active { form { - @extend .form-control:focus; border-color: $blue-300; box-shadow: none; diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index c22a1ae1187..b8183a0d82f 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -83,7 +83,6 @@ } } -.gl-text-purple { color: $purple; } .gl-bg-purple-light { background-color: $purple-light; } // move this to GitLab UI once onboarding experiment is considered a success diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 2032d1e95a6..e617b4358e3 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -1,5 +1,5 @@ .search.search-form{ data: { track_label: "navbar_search", track_event: "activate_form_input", track_value: "" } } - = form_tag search_path, method: :get, class: 'form-inline' do |_f| + = form_tag search_path, method: :get, class: 'form-inline form-control' do |_f| .search-input-container .search-input-wrap .dropdown{ data: { url: search_autocomplete_path } } diff --git a/changelogs/unreleased/330402-remove-unicorn-gitlab-health-check.yml b/changelogs/unreleased/330402-remove-unicorn-gitlab-health-check.yml new file mode 100644 index 00000000000..4867f75652e --- /dev/null +++ b/changelogs/unreleased/330402-remove-unicorn-gitlab-health-check.yml @@ -0,0 +1,5 @@ +--- +title: Remove UnicornCheck service +merge_request: 62204 +author: +type: removed diff --git a/changelogs/unreleased/sh-smplify-external-validation-error-codes.yml b/changelogs/unreleased/sh-smplify-external-validation-error-codes.yml new file mode 100644 index 00000000000..4f6ab6ea472 --- /dev/null +++ b/changelogs/unreleased/sh-smplify-external-validation-error-codes.yml @@ -0,0 +1,5 @@ +--- +title: Simplify error code handling for external pipeline validation +merge_request: 61190 +author: +type: changed diff --git a/config/initializers/cluster_events_before_phased_restart.rb b/config/initializers/cluster_events_before_phased_restart.rb index d029adbe363..f84682c1436 100644 --- a/config/initializers/cluster_events_before_phased_restart.rb +++ b/config/initializers/cluster_events_before_phased_restart.rb @@ -7,8 +7,6 @@ # # Follow-up the issue: https://gitlab.com/gitlab-org/gitlab/issues/34107 -if Gitlab::Runtime.puma? - Puma::Cluster.prepend(::Gitlab::Cluster::Mixins::PumaCluster) -elsif Gitlab::Runtime.unicorn? - Unicorn::HttpServer.prepend(::Gitlab::Cluster::Mixins::UnicornHttpServer) -end +return unless Gitlab::Runtime.puma? + +Puma::Cluster.prepend(::Gitlab::Cluster::Mixins::PumaCluster) diff --git a/doc/administration/external_pipeline_validation.md b/doc/administration/external_pipeline_validation.md index 89543e446ac..23207de4999 100644 --- a/doc/administration/external_pipeline_validation.md +++ b/doc/administration/external_pipeline_validation.md @@ -17,7 +17,7 @@ data as payload. The response code from the external service determines if GitLa should accept or reject the pipeline. If the response is: - `200`, the pipeline is accepted. -- `4XX`, the pipeline is rejected. +- `406`, the pipeline is rejected. - Other codes, the pipeline is accepted and logged. If there's an error or the request times out, the pipeline is accepted. diff --git a/doc/install/installation.md b/doc/install/installation.md index 3af3d2bfe7a..b9bde01c2fa 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -45,6 +45,15 @@ You can select the branch in the version dropdown in the top left corner of GitL If the highest number stable branch is unclear, check the [GitLab blog](https://about.gitlab.com/blog/) for installation guide links by version. +## Software requirements + +| Software | Minimum version | Notes | +| -------- | --------------- | ----- | +| [Ruby](#2-ruby) | `2.7` | From GitLab 13.6, Ruby 2.7 is required. Ruby 3.0 is not supported yet (see [the relevant epic](https://gitlab.com/groups/gitlab-org/-/epics/5149) for the current status). You must use the standard MRI implementation of Ruby. We love [JRuby](https://www.jruby.org/) and [Rubinius](https://github.com/rubinius/rubinius#the-rubinius-language-platform), but GitLab needs several Gems that have native extensions. | +| [Go](#3-go) | `1.15` | | +| [Git](#git) | `2.31.x` | From GitLab 13.11, Git 2.31.x and later is required. It's highly recommended that you use the [Git version provided by Gitaly](#git). | +| [Node.js](#4-node) | `12.22.1` | GitLab uses [webpack](https://webpack.js.org/) to compile frontend assets. Node.js 14.x is recommended, as it's faster. You can check which version you're running with `node -v`. You need to update it to a newer version if needed. | + ## GitLab directory structure This is the main directory structure you end up with following the instructions @@ -207,7 +216,7 @@ sudo apt-get install -y libimage-exiftool-perl ## 2. Ruby The Ruby interpreter is required to run GitLab. -See the [requirements page](requirements.md#ruby-versions) for the minimum +See the [requirements section of this page](#software-requirements) for the minimum Ruby requirements. The use of Ruby version managers such as [`RVM`](https://rvm.io/), [`rbenv`](https://github.com/rbenv/rbenv) or [`chruby`](https://github.com/postmodern/chruby) with GitLab diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 9aa06030c24..5c723ee06cd 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -47,55 +47,6 @@ Please consider using a virtual machine to run GitLab. ## Software requirements -### Ruby versions - -From GitLab 13.6: - -- Ruby 2.7 and later is required. - -From GitLab 12.2: - -- Ruby 2.6 and later is required. - -You must use the standard MRI implementation of Ruby. -We love [JRuby](https://www.jruby.org/) and [Rubinius](https://github.com/rubinius/rubinius#the-rubinius-language-platform), but GitLab -needs several Gems that have native extensions. - -### Go versions - -The minimum required Go version is 1.13. - -### Git versions - -From GitLab 13.11: - -- Git 2.31.x and later is required. We recommend you use the - [Git version provided by Gitaly](installation.md#git). - -From GitLab 13.6: - -- Git 2.29.x and later is required. - -From GitLab 13.1: - -- Git 2.24.x and later is required. -- Git 2.28.x and later [is recommended](https://gitlab.com/gitlab-org/gitaly/-/issues/2959). - -### Node.js versions - -Beginning in GitLab 12.9, we only support Node.js 10.13.0 or higher, and we have dropped -support for Node.js 8. (Node.js 6 support was dropped in GitLab 11.8) - -We recommend Node 14.x, as it's faster. - -GitLab uses [webpack](https://webpack.js.org/) to compile frontend assets, which requires a minimum -version of Node.js 10.13.0. - -You can check which version you're running with `node -v`. If you're running -a version older than `v10.13.0`, you need to update it to a newer version. You -can find instructions to install from community maintained packages or compile -from source at the [Node.js website](https://nodejs.org/en/download/). - ### Redis versions GitLab 13.0 and later requires Redis version 4.0 or higher. diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md index 93d2c2cb288..78eb6fab10b 100644 --- a/doc/update/upgrading_from_source.md +++ b/doc/update/upgrading_from_source.md @@ -82,7 +82,7 @@ sudo make install ### 4. Update Node.js -To check the minimum required Node.js version, see [Node.js versions](../install/requirements.md#nodejs-versions). +To check the minimum required Node.js version, see [Node.js versions](../install/installation.md#software-requirements). GitLab also requires the use of Yarn `>= v1.10.0` to manage JavaScript dependencies. @@ -99,7 +99,7 @@ More information can be found on the [Yarn website](https://classic.yarnpkg.com/ ### 5. Update Go -To check the minimum required Go version, see [Go versions](../install/requirements.md#go-versions). +To check the minimum required Go version, see [Go versions](../install/installation.md#software-requirements). You can check which version you are running with `go version`. @@ -119,12 +119,8 @@ rm go1.13.5.linux-amd64.tar.gz ### 6. Update Git -WARNING: -From GitLab 13.1, you must use at least Git v2.24 (previous minimum version was v2.22). -Git v2.28 is recommended. - To check you are running the minimum required Git version, see -[Git versions](../install/requirements.md#git-versions). +[Git versions](../install/installation.md#software-requirements). From GitLab 13.6, we recommend you use the [Git version provided by Gitaly](https://gitlab.com/gitlab-org/gitaly/-/issues/2729) diff --git a/lib/gitlab/ci/pipeline/chain/validate/external.rb b/lib/gitlab/ci/pipeline/chain/validate/external.rb index e8fe3b4f797..18675f80279 100644 --- a/lib/gitlab/ci/pipeline/chain/validate/external.rb +++ b/lib/gitlab/ci/pipeline/chain/validate/external.rb @@ -12,8 +12,7 @@ module Gitlab DEFAULT_VALIDATION_REQUEST_TIMEOUT = 5 ACCEPTED_STATUS = 200 - DOT_COM_REJECTED_STATUS = 406 - GENERAL_REJECTED_STATUS = (400..499).freeze + REJECTED_STATUS = 406 def perform! pipeline_authorized = validate_external @@ -34,14 +33,13 @@ module Gitlab return true unless validation_service_url # 200 - accepted - # 406 - not accepted on GitLab.com - # 4XX - not accepted for other installations + # 406 - rejected # everything else - accepted and logged response_code = validate_service_request.code case response_code when ACCEPTED_STATUS true - when rejected_status + when REJECTED_STATUS false else raise InvalidResponseCode, "Unsupported response code received from Validation Service: #{response_code}" @@ -52,14 +50,6 @@ module Gitlab true end - def rejected_status - if Gitlab.com? - DOT_COM_REJECTED_STATUS - else - GENERAL_REJECTED_STATUS - end - end - def validate_service_request headers = { 'X-Gitlab-Correlation-id' => Labkit::Correlation::CorrelationId.current_id, diff --git a/lib/gitlab/cluster/mixins/unicorn_http_server.rb b/lib/gitlab/cluster/mixins/unicorn_http_server.rb deleted file mode 100644 index 440ed02a355..00000000000 --- a/lib/gitlab/cluster/mixins/unicorn_http_server.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Cluster - module Mixins - module UnicornHttpServer - def self.prepended(base) - unless base.method_defined?(:reexec) && base.method_defined?(:stop) - raise 'missing method Unicorn::HttpServer#reexec or Unicorn::HttpServer#stop' - end - end - - def reexec - Gitlab::Cluster::LifecycleEvents.do_before_graceful_shutdown - - super - end - - # The stop on non-graceful shutdown is executed twice: - # `#stop(false)` and `#stop`. - # - # The first stop will wipe-out all workers, so we need to check - # the flag and a list of workers - def stop(graceful = true) - if graceful && @workers.any? # rubocop:disable Gitlab/ModuleWithInstanceVariables - Gitlab::Cluster::LifecycleEvents.do_before_graceful_shutdown - end - - super - end - end - end - end -end diff --git a/lib/gitlab/health_checks/unicorn_check.rb b/lib/gitlab/health_checks/unicorn_check.rb deleted file mode 100644 index f0c6fdab600..00000000000 --- a/lib/gitlab/health_checks/unicorn_check.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module HealthChecks - # This check can only be run on Unicorn `master` process - class UnicornCheck - extend SimpleAbstractCheck - - class << self - include Gitlab::Utils::StrongMemoize - - private - - def metric_prefix - 'unicorn_check' - end - - def successful?(result) - result > 0 - end - - def check - return unless http_servers - - http_servers.sum(&:worker_processes) - end - - # Traversal of ObjectSpace is expensive, on fully loaded application - # it takes around 80ms. The instances of HttpServers are not a subject - # to change so we can cache the list of servers. - def http_servers - strong_memoize(:http_servers) do - next unless Gitlab::Runtime.unicorn? - - ObjectSpace.each_object(::Unicorn::HttpServer).to_a - end - end - end - end - end -end diff --git a/lib/gitlab/metrics/exporter/web_exporter.rb b/lib/gitlab/metrics/exporter/web_exporter.rb index 558454eaa1c..756e6b0641a 100644 --- a/lib/gitlab/metrics/exporter/web_exporter.rb +++ b/lib/gitlab/metrics/exporter/web_exporter.rb @@ -30,8 +30,7 @@ module Gitlab # application: https://gitlab.com/gitlab-org/gitlab/issues/35343 self.readiness_checks = [ WebExporter::ExporterCheck.new(self), - Gitlab::HealthChecks::PumaCheck, - Gitlab::HealthChecks::UnicornCheck + Gitlab::HealthChecks::PumaCheck ] end diff --git a/package.json b/package.json index d90dccf2820..74780bac936 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "@gitlab/favicon-overlay": "2.0.0", "@gitlab/svgs": "1.197.0", "@gitlab/tributejs": "1.0.0", - "@gitlab/ui": "29.27.0", + "@gitlab/ui": "29.28.0", "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "^6.0.3-4", "@rails/ujs": "^6.0.3-4", diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb index 81ad1896075..b998ad24358 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb @@ -16,7 +16,7 @@ module QA Flow::Login.sign_in end - it 'creates a basic merge request', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1276' do + it 'creates a basic merge request', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1850' do Resource::MergeRequest.fabricate_via_browser_ui! do |merge_request| merge_request.project = project merge_request.title = merge_request_title diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb index 2c0fb5ea290..67b48d254ac 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb @@ -3,7 +3,7 @@ module QA RSpec.describe 'Create' do describe 'Git push over HTTP', :smoke do - it 'user using a personal access token pushes code to the repository', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1278' do + it 'user using a personal access token pushes code to the repository', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1848' do Flow::Login.sign_in access_token = Resource::PersonalAccessToken.fabricate!.token diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb index 8617e05f912..cdb64361549 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb @@ -24,7 +24,7 @@ module QA runner.remove_via_api! end - it 'users creates a pipeline which gets processed', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1279' do + it 'users creates a pipeline which gets processed', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1849' do Flow::Login.sign_in Resource::Repository::Commit.fabricate_via_api! do |commit| diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb index e2cf5c5b195..44888408f4e 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb @@ -115,7 +115,7 @@ module QA end end - it 'runs an AutoDevOps pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1277' do + it 'runs an AutoDevOps pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1847' do Flow::Pipeline.visit_latest_pipeline Page::Project::Pipeline::Show.perform do |pipeline| diff --git a/rubocop/cop/usage_data/instrumentation_superclass.rb b/rubocop/cop/usage_data/instrumentation_superclass.rb new file mode 100644 index 00000000000..2ff2ed47a23 --- /dev/null +++ b/rubocop/cop/usage_data/instrumentation_superclass.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module UsageData + # This cop checks that metric instrumentation classes subclass one of the allowed base classes. + # + # @example + # + # # good + # class CountIssues < DatabaseMetric + # # ... + # end + # + # # bad + # class CountIssues < BaseMetric + # # ... + # end + class InstrumentationSuperclass < RuboCop::Cop::Cop + MSG = "Instrumentation classes should subclass one of the following: %{allowed_classes}." + + BASE_PATTERN = "(const nil? !#allowed_class?)" + + def_node_matcher :class_definition, <<~PATTERN + (class (const _ !#allowed_class?) #{BASE_PATTERN} ...) + PATTERN + + def_node_matcher :class_new_definition, <<~PATTERN + [!^(casgn {nil? cbase} #allowed_class? ...) + !^^(casgn {nil? cbase} #allowed_class? (block ...)) + (send (const {nil? cbase} :Class) :new #{BASE_PATTERN})] + PATTERN + + def on_class(node) + class_definition(node) do + register_offense(node.children[1]) + end + end + + def on_send(node) + class_new_definition(node) do + register_offense(node.children.last) + end + end + + private + + def allowed_class?(class_name) + allowed_classes.include?(class_name) + end + + def allowed_classes + cop_config['AllowedClasses'] || [] + end + + def register_offense(offense_node) + message = format(MSG, allowed_classes: allowed_classes.join(', ')) + add_offense(offense_node, message: message) + end + end + end + end +end diff --git a/rubocop/rubocop-usage-data.yml b/rubocop/rubocop-usage-data.yml index a03f21e491a..5b88a3349c8 100644 --- a/rubocop/rubocop-usage-data.yml +++ b/rubocop/rubocop-usage-data.yml @@ -57,3 +57,11 @@ UsageData/DistinctCountByLargeForeignKey: - 'user_id' - 'resource_owner_id' - 'requirement_id' +UsageData/InstrumentationSuperclass: + Enabled: true + Include: + - 'lib/gitlab/usage/metrics/instrumentations/**/*.rb' + AllowedClasses: + - :DatabaseMetric + - :GenericMetric + - :RedisHLLMetric diff --git a/spec/features/issues/csv_spec.rb b/spec/features/issues/csv_spec.rb index d41a41c4383..51e0d54ca5e 100644 --- a/spec/features/issues/csv_spec.rb +++ b/spec/features/issues/csv_spec.rb @@ -16,9 +16,7 @@ RSpec.describe 'Issues csv', :js do def request_csv(params = {}) visit project_issues_path(project, params) - page.within('.nav-controls') do - find('[data-testid="export-csv-button"]').click - end + click_button 'Export as CSV' click_on 'Export issues' end diff --git a/spec/features/issues/user_resets_their_incoming_email_token_spec.rb b/spec/features/issues/user_resets_their_incoming_email_token_spec.rb index 2b1c25174c2..9e47639d80f 100644 --- a/spec/features/issues/user_resets_their_incoming_email_token_spec.rb +++ b/spec/features/issues/user_resets_their_incoming_email_token_spec.rb @@ -16,11 +16,11 @@ RSpec.describe 'Issues > User resets their incoming email token' do end it 'changes incoming email address token', :js do - page.find('[data-testid="issuable-email-modal-btn"]').click + click_button 'Email a new issue to this project' page.within '#issuable-email-modal' do previous_token = page.find('input[type="text"]').value - page.find('[data-testid="incoming-email-token-reset"]').click + click_button 'reset it' wait_for_requests diff --git a/spec/features/merge_requests/user_exports_as_csv_spec.rb b/spec/features/merge_requests/user_exports_as_csv_spec.rb index 725b8366d04..351e714b612 100644 --- a/spec/features/merge_requests/user_exports_as_csv_spec.rb +++ b/spec/features/merge_requests/user_exports_as_csv_spec.rb @@ -12,15 +12,9 @@ RSpec.describe 'Merge Requests > Exports as CSV', :js do visit(project_merge_requests_path(project)) end - subject { page.find('.nav-controls') } - - it { is_expected.to have_selector '[data-testid="export-csv-button"]' } - context 'button is clicked' do before do - page.within('.nav-controls') do - find('[data-testid="export-csv-button"]').click - end + click_button 'Export as CSV' end it 'shows a success message' do diff --git a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb index bf90e86c263..862bae45fc6 100644 --- a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb +++ b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb @@ -116,7 +116,8 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do click_on('Save changes') end - find('.flash-notice') + wait_for_all_requests + checkbox = find_field('project_printing_merge_request_link_enabled') expect(checkbox).not_to be_checked @@ -139,7 +140,8 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do click_on('Save changes') end - find('.flash-notice') + wait_for_all_requests + checkbox = find_field('project_remove_source_branch_after_merge') expect(checkbox).not_to be_checked diff --git a/spec/frontend/issuable/components/csv_export_modal_spec.js b/spec/frontend/issuable/components/csv_export_modal_spec.js index 7eb85a946ae..34094d22e68 100644 --- a/spec/frontend/issuable/components/csv_export_modal_spec.js +++ b/spec/frontend/issuable/components/csv_export_modal_spec.js @@ -1,7 +1,6 @@ import { GlModal, GlIcon, GlButton } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import { stubComponent } from 'helpers/stub_component'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import CsvExportModal from '~/issuable/components/csv_export_modal.vue'; describe('CsvExportModal', () => { @@ -9,26 +8,24 @@ describe('CsvExportModal', () => { function createComponent(options = {}) { const { injectedProperties = {}, props = {} } = options; - return extendedWrapper( - mount(CsvExportModal, { - propsData: { - modalId: 'csv-export-modal', - exportCsvPath: 'export/csv/path', - issuableCount: 1, - ...props, - }, - provide: { - issuableType: 'issues', - ...injectedProperties, - }, - stubs: { - GlModal: stubComponent(GlModal, { - template: - '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>', - }), - }, - }), - ); + return mount(CsvExportModal, { + propsData: { + modalId: 'csv-export-modal', + exportCsvPath: 'export/csv/path', + issuableCount: 1, + ...props, + }, + provide: { + issuableType: 'issues', + ...injectedProperties, + }, + stubs: { + GlModal: stubComponent(GlModal, { + template: + '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>', + }), + }, + }); } afterEach(() => { @@ -61,14 +58,13 @@ describe('CsvExportModal', () => { describe('issuable count info text', () => { it('displays the info text when issuableCount is > -1', () => { wrapper = createComponent({ props: { issuableCount: 10 } }); - expect(wrapper.findByTestId('issuable-count-note').exists()).toBe(true); - expect(wrapper.findByTestId('issuable-count-note').text()).toContain('10 issues selected'); + expect(wrapper.text()).toContain('10 issues selected'); expect(findIcon().exists()).toBe(true); }); it("doesn't display the info text when issuableCount is -1", () => { wrapper = createComponent({ props: { issuableCount: -1 } }); - expect(wrapper.findByTestId('issuable-count-note').exists()).toBe(false); + expect(wrapper.text()).not.toContain('issues selected'); }); }); diff --git a/spec/frontend/issuable/components/csv_import_export_buttons_spec.js b/spec/frontend/issuable/components/csv_import_export_buttons_spec.js index 2fe8d28a333..118c12d968b 100644 --- a/spec/frontend/issuable/components/csv_import_export_buttons_spec.js +++ b/spec/frontend/issuable/components/csv_import_export_buttons_spec.js @@ -1,6 +1,6 @@ -import { shallowMount } from '@vue/test-utils'; +import { GlButton, GlDropdown } from '@gitlab/ui'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import CsvExportModal from '~/issuable/components/csv_export_modal.vue'; import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue'; import CsvImportModal from '~/issuable/components/csv_import_modal.vue'; @@ -14,35 +14,33 @@ describe('CsvImportExportButtons', () => { function createComponent(injectedProperties = {}) { glModalDirective = jest.fn(); - return extendedWrapper( - shallowMount(CsvImportExportButtons, { - directives: { - GlTooltip: createMockDirective(), - glModal: { - bind(_, { value }) { - glModalDirective(value); - }, + return mountExtended(CsvImportExportButtons, { + directives: { + GlTooltip: createMockDirective(), + glModal: { + bind(_, { value }) { + glModalDirective(value); }, }, - provide: { - ...injectedProperties, - }, - propsData: { - exportCsvPath, - issuableCount, - }, - }), - ); + }, + provide: { + ...injectedProperties, + }, + propsData: { + exportCsvPath, + issuableCount, + }, + }); } afterEach(() => { wrapper.destroy(); }); - const findExportCsvButton = () => wrapper.findByTestId('export-csv-button'); - const findImportDropdown = () => wrapper.findByTestId('import-csv-dropdown'); - const findImportCsvButton = () => wrapper.findByTestId('import-csv-dropdown'); - const findImportFromJiraLink = () => wrapper.findByTestId('import-from-jira-link'); + const findExportCsvButton = () => wrapper.findComponent(GlButton); + const findImportDropdown = () => wrapper.findComponent(GlDropdown); + const findImportCsvButton = () => wrapper.findByRole('menuitem', { name: 'Import CSV' }); + const findImportFromJiraLink = () => wrapper.findByRole('menuitem', { name: 'Import from Jira' }); const findExportCsvModal = () => wrapper.findComponent(CsvExportModal); const findImportCsvModal = () => wrapper.findComponent(CsvImportModal); @@ -97,7 +95,7 @@ describe('CsvImportExportButtons', () => { expect(findImportDropdown().exists()).toBe(true); }); - it('renders the import button', () => { + it('renders the import csv menu item', () => { expect(findImportCsvButton().exists()).toBe(true); }); @@ -106,8 +104,11 @@ describe('CsvImportExportButtons', () => { wrapper = createComponent({ showImportButton: true, showLabel: false }); }); - it('does not have a button text', () => { - expect(findImportCsvButton().props('text')).toBe(null); + it('hides button text', () => { + expect(findImportDropdown().props()).toMatchObject({ + text: 'Import issues', + textSrOnly: true, + }); }); it('import button has a tooltip', () => { @@ -124,7 +125,10 @@ describe('CsvImportExportButtons', () => { }); it('displays a button text', () => { - expect(findImportCsvButton().props('text')).toBe('Import issues'); + expect(findImportDropdown().props()).toMatchObject({ + text: 'Import issues', + textSrOnly: false, + }); }); it('import button has no tooltip', () => { diff --git a/spec/frontend/issuable/components/csv_import_modal_spec.js b/spec/frontend/issuable/components/csv_import_modal_spec.js index ce9d738f77b..0c88b6b1283 100644 --- a/spec/frontend/issuable/components/csv_import_modal_spec.js +++ b/spec/frontend/issuable/components/csv_import_modal_spec.js @@ -1,8 +1,6 @@ -import { GlModal } from '@gitlab/ui'; -import { getByRole, getByLabelText } from '@testing-library/dom'; -import { mount } from '@vue/test-utils'; +import { GlButton, GlModal } from '@gitlab/ui'; import { stubComponent } from 'helpers/stub_component'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import CsvImportModal from '~/issuable/components/csv_import_modal.vue'; jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' })); @@ -13,23 +11,21 @@ describe('CsvImportModal', () => { function createComponent(options = {}) { const { injectedProperties = {}, props = {} } = options; - return extendedWrapper( - mount(CsvImportModal, { - propsData: { - modalId: 'csv-import-modal', - ...props, - }, - provide: { - issuableType: 'issues', - ...injectedProperties, - }, - stubs: { - GlModal: stubComponent(GlModal, { - template: '<div><slot></slot><slot name="modal-footer"></slot></div>', - }), - }, - }), - ); + return mountExtended(CsvImportModal, { + propsData: { + modalId: 'csv-import-modal', + ...props, + }, + provide: { + issuableType: 'issues', + ...injectedProperties, + }, + stubs: { + GlModal: stubComponent(GlModal, { + template: '<div><slot></slot><slot name="modal-footer"></slot></div>', + }), + }, + }); } beforeEach(() => { @@ -41,9 +37,9 @@ describe('CsvImportModal', () => { }); const findModal = () => wrapper.findComponent(GlModal); - const findPrimaryButton = () => getByRole(wrapper.element, 'button', { name: 'Import issues' }); - const findForm = () => wrapper.findByTestId('import-csv-form'); - const findFileInput = () => getByLabelText(wrapper.element, 'Upload CSV file'); + const findPrimaryButton = () => wrapper.findComponent(GlButton); + const findForm = () => wrapper.find('form'); + const findFileInput = () => wrapper.findByLabelText('Upload CSV file'); const findAuthenticityToken = () => new FormData(findForm().element).get('authenticity_token'); describe('template', () => { @@ -76,8 +72,8 @@ describe('CsvImportModal', () => { expect(findPrimaryButton()).toExist(); }); - it('submits the form when the primary action is clicked', async () => { - findPrimaryButton().click(); + it('submits the form when the primary action is clicked', () => { + findPrimaryButton().trigger('click'); expect(formSubmitSpy).toHaveBeenCalled(); }); diff --git a/spec/frontend/issuable/components/issuable_by_email_spec.js b/spec/frontend/issuable/components/issuable_by_email_spec.js index 08a99f29479..f11c41fe25d 100644 --- a/spec/frontend/issuable/components/issuable_by_email_spec.js +++ b/spec/frontend/issuable/components/issuable_by_email_spec.js @@ -58,10 +58,11 @@ describe('IssuableByEmail', () => { mockAxios.restore(); }); - const findFormInputGroup = () => wrapper.find(GlFormInputGroup); + const findButton = () => wrapper.findComponent(GlButton); + const findFormInputGroup = () => wrapper.findComponent(GlFormInputGroup); const clickResetEmail = async () => { - wrapper.findByTestId('incoming-email-token-reset').vm.$emit('click'); + wrapper.findAllComponents(GlButton).at(2).trigger('click'); await waitForPromises(); }; @@ -75,14 +76,14 @@ describe('IssuableByEmail', () => { 'renders a link with "$buttonText" when type is "$issuableType"', ({ issuableType, buttonText }) => { wrapper = createComponent({ issuableType }); - expect(wrapper.findByTestId('issuable-email-modal-btn').text()).toBe(buttonText); + expect(findButton().text()).toBe(buttonText); }, ); it('opens the modal when the user clicks the button', () => { wrapper = createComponent(); - wrapper.findByTestId('issuable-email-modal-btn').vm.$emit('click'); + findButton().trigger('click'); expect(glModalDirective).toHaveBeenCalled(); }); @@ -105,7 +106,7 @@ describe('IssuableByEmail', () => { initialEmail, }); - expect(wrapper.findByTestId('mail-to-btn').attributes('href')).toBe( + expect(wrapper.findAllComponents(GlButton).at(1).attributes('href')).toBe( `mailto:${initialEmail}?subject=${subject}&body=${body}`, ); }); diff --git a/spec/frontend/issuable/components/status_box_spec.js b/spec/frontend/issuable/components/status_box_spec.js index 990fac67f7e..9cbf023dbd6 100644 --- a/spec/frontend/issuable/components/status_box_spec.js +++ b/spec/frontend/issuable/components/status_box_spec.js @@ -1,4 +1,4 @@ -import { GlSprintf } from '@gitlab/ui'; +import { GlIcon, GlSprintf } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import StatusBox from '~/issuable/components/status_box.vue'; @@ -64,7 +64,7 @@ describe('Merge request status box component', () => { initialState: testCase.state, }); - expect(wrapper.find('[data-testid="status-icon"]').props('name')).toBe(testCase.icon); + expect(wrapper.findComponent(GlIcon).props('name')).toBe(testCase.icon); }); }); }); diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js index e12cd8b0e37..8a04e93e88c 100644 --- a/spec/frontend/lib/utils/url_utility_spec.js +++ b/spec/frontend/lib/utils/url_utility_spec.js @@ -798,15 +798,29 @@ describe('URL utility', () => { ); }); - it('handles arrays properly', () => { + it('adds parameters from arrays', () => { const url = 'https://gitlab.com/test'; - expect(urlUtils.setUrlParams({ label_name: ['foo', 'bar'] }, url)).toEqual( - 'https://gitlab.com/test?label_name=foo&label_name=bar', + expect(urlUtils.setUrlParams({ labels: ['foo', 'bar'] }, url)).toEqual( + 'https://gitlab.com/test?labels=foo&labels=bar', ); }); - it('handles arrays properly when railsArraySyntax=true', () => { + it('removes parameters from empty arrays', () => { + const url = 'https://gitlab.com/test?labels=foo&labels=bar'; + + expect(urlUtils.setUrlParams({ labels: [] }, url)).toEqual('https://gitlab.com/test'); + }); + + it('removes parameters from empty arrays while keeping other parameters', () => { + const url = 'https://gitlab.com/test?labels=foo&labels=bar&unrelated=unrelated'; + + expect(urlUtils.setUrlParams({ labels: [] }, url)).toEqual( + 'https://gitlab.com/test?unrelated=unrelated', + ); + }); + + it('adds parameters from arrays when railsArraySyntax=true', () => { const url = 'https://gitlab.com/test'; expect(urlUtils.setUrlParams({ labels: ['foo', 'bar'] }, url, false, true)).toEqual( @@ -814,6 +828,14 @@ describe('URL utility', () => { ); }); + it('removes parameters from empty arrays when railsArraySyntax=true', () => { + const url = 'https://gitlab.com/test?labels%5B%5D=foo&labels%5B%5D=bar'; + + expect(urlUtils.setUrlParams({ labels: [] }, url, false, true)).toEqual( + 'https://gitlab.com/test', + ); + }); + it('decodes URI when decodeURI=true', () => { const url = 'https://gitlab.com/test'; diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb index 58ad3d1b4d1..84377981cbc 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb @@ -43,7 +43,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do end let(:save_incompleted) { true } - let(:dot_com) { true } let(:command) do Gitlab::Ci::Pipeline::Chain::Command.new( project: project, current_user: user, yaml_processor_result: yaml_processor_result, save_incompleted: save_incompleted @@ -57,7 +56,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do before do stub_env('EXTERNAL_VALIDATION_SERVICE_URL', validation_service_url) - allow(Gitlab).to receive(:com?).and_return(dot_com) allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('correlation-id') end @@ -199,34 +197,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do end end - context 'when not on .com' do - let(:dot_com) { false } - - before do - stub_request(:post, validation_service_url).to_return(status: 404, body: "{}") - end - - it 'drops the pipeline' do - perform! - - expect(pipeline.status).to eq('failed') - expect(pipeline).to be_persisted - expect(pipeline.errors.to_a).to include('External validation failed') - end - - it 'breaks the chain' do - perform! - - expect(step.break?).to be true - end - - it 'logs the authorization' do - expect(Gitlab::AppLogger).to receive(:info).with(message: 'Pipeline not authorized', project_id: project.id, user_id: user.id) - - perform! - end - end - context 'when validation returns 406 Not Acceptable' do before do stub_request(:post, validation_service_url).to_return(status: 406, body: "{}") diff --git a/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb b/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb deleted file mode 100644 index 7f7c95b2527..00000000000 --- a/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb +++ /dev/null @@ -1,117 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -# For easier debugging set `UNICORN_DEBUG=1` - -RSpec.describe Gitlab::Cluster::Mixins::UnicornHttpServer do - before do - stub_const('UNICORN_STARTUP_TIMEOUT', 30) - end - - context 'when running Unicorn' do - using RSpec::Parameterized::TableSyntax - - where(:signal, :exitstatus, :termsig) do - # executes phased restart block - :USR2 | 140 | nil - :QUIT | 140 | nil - - # does not execute phased restart block - :INT | 0 | nil - :TERM | 0 | nil - end - - with_them do - it 'properly handles process lifecycle' do - with_unicorn(workers: 1) do |pid| - Process.kill(signal, pid) - - child_pid, child_status = Process.wait2(pid) - expect(child_pid).to eq(pid) - expect(child_status.exitstatus).to eq(exitstatus) - expect(child_status.termsig).to eq(termsig) - end - end - end - end - - private - - def with_unicorn(workers:, timeout: UNICORN_STARTUP_TIMEOUT) - with_unicorn_configs(workers: workers) do |unicorn_rb, config_ru| - cmdline = [ - "bundle", "exec", "unicorn", - "-I", Rails.root.to_s, - "-c", unicorn_rb, - config_ru - ] - - IO.popen(cmdline) do |process| - # wait for process to start: - # I, [2019-10-15T13:21:27.565225 #3089] INFO -- : master process ready - wait_for_output(process, /master process ready/, timeout: timeout) - consume_output(process) - - yield(process.pid) - ensure - begin - Process.kill(:KILL, process.pid) - rescue Errno::ESRCH - end - end - end - end - - def with_unicorn_configs(workers:) - Dir.mktmpdir do |dir| - File.write "#{dir}/unicorn.rb", <<-EOF - require './lib/gitlab/cluster/lifecycle_events' - require './lib/gitlab/cluster/mixins/unicorn_http_server' - - worker_processes #{workers} - listen "127.0.0.1:0" - preload_app true - - Unicorn::HttpServer.prepend(#{described_class}) - - mutex = Mutex.new - - Gitlab::Cluster::LifecycleEvents.on_before_blackout_period do - mutex.synchronize do - exit(140) - end - end - - # redirect stderr to stdout - $stderr.reopen($stdout) - EOF - - File.write "#{dir}/config.ru", <<-EOF - run -> (env) { [404, {}, ['']] } - EOF - - yield("#{dir}/unicorn.rb", "#{dir}/config.ru") - end - end - - def wait_for_output(process, output, timeout:) - Timeout.timeout(timeout) do - loop do - line = process.readline - puts "UNICORN_DEBUG: #{line}" if ENV['UNICORN_DEBUG'] - break if line =~ output - end - end - end - - def consume_output(process) - Thread.new do - loop do - line = process.readline - puts "UNICORN_DEBUG: #{line}" if ENV['UNICORN_DEBUG'] - end - rescue StandardError - end - end -end diff --git a/spec/lib/gitlab/health_checks/unicorn_check_spec.rb b/spec/lib/gitlab/health_checks/unicorn_check_spec.rb deleted file mode 100644 index 1cc44016002..00000000000 --- a/spec/lib/gitlab/health_checks/unicorn_check_spec.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::HealthChecks::UnicornCheck do - let(:result_class) { Gitlab::HealthChecks::Result } - let(:readiness) { described_class.readiness } - let(:metrics) { described_class.metrics } - - before do - described_class.clear_memoization(:http_servers) - end - - shared_examples 'with state' do |(state, message)| - it "does provide readiness" do - expect(readiness).to eq(result_class.new('unicorn_check', state, message)) - end - - it "does provide metrics" do - expect(metrics).to include( - an_object_having_attributes(name: 'unicorn_check_success', value: state ? 1 : 0)) - expect(metrics).to include( - an_object_having_attributes(name: 'unicorn_check_latency_seconds', value: be >= 0)) - end - end - - context 'when Unicorn is not loaded' do - before do - allow(Gitlab::Runtime).to receive(:unicorn?).and_return(false) - hide_const('Unicorn') - end - - it "does not provide readiness and metrics" do - expect(readiness).to be_nil - expect(metrics).to be_nil - end - end - - context 'when Unicorn is loaded' do - let(:http_server_class) { Struct.new(:worker_processes) } - - before do - allow(Gitlab::Runtime).to receive(:unicorn?).and_return(true) - stub_const('Unicorn::HttpServer', http_server_class) - end - - context 'when no servers are running' do - it_behaves_like 'with state', [false, 'unexpected Unicorn check result: 0'] - end - - context 'when servers without workers are running' do - before do - http_server_class.new(0) - end - - it_behaves_like 'with state', [false, 'unexpected Unicorn check result: 0'] - end - - context 'when servers with workers are running' do - before do - http_server_class.new(1) - end - - it_behaves_like 'with state', true - end - end -end diff --git a/spec/rubocop/cop/usage_data/instrumentation_superclass_spec.rb b/spec/rubocop/cop/usage_data/instrumentation_superclass_spec.rb new file mode 100644 index 00000000000..31324331e61 --- /dev/null +++ b/spec/rubocop/cop/usage_data/instrumentation_superclass_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +require_relative '../../../../rubocop/cop/usage_data/instrumentation_superclass' + +RSpec.describe RuboCop::Cop::UsageData::InstrumentationSuperclass do + let(:allowed_classes) { %i[GenericMetric DatabaseMetric RedisHllMetric] } + let(:msg) { "Instrumentation classes should subclass one of the following: #{allowed_classes.join(', ')}." } + + let(:config) do + RuboCop::Config.new('UsageData/InstrumentationSuperclass' => { + 'AllowedClasses' => allowed_classes + }) + end + + subject(:cop) { described_class.new(config) } + + context 'with class definition' do + context 'when inheriting from allowed superclass' do + it 'does not register an offense' do + expect_no_offenses('class NewMetric < GenericMetric; end') + end + end + + context 'when inheriting from some other superclass' do + it 'registers an offense' do + expect_offense(<<~CODE) + class NewMetric < BaseMetric; end + ^^^^^^^^^^ #{msg} + CODE + end + end + + context 'when not inheriting' do + it 'does not register an offense' do + expect_no_offenses('class NewMetric; end') + end + end + end + + context 'with dynamic class definition' do + context 'when inheriting from allowed superclass' do + it 'does not register an offense' do + expect_no_offenses('NewMetric = Class.new(GenericMetric)') + end + end + + context 'when inheriting from some other superclass' do + it 'registers an offense' do + expect_offense(<<~CODE) + NewMetric = Class.new(BaseMetric) + ^^^^^^^^^^ #{msg} + CODE + end + end + + context 'when not inheriting' do + it 'does not register an offense' do + expect_no_offenses('NewMetric = Class.new') + end + end + end +end diff --git a/yarn.lock b/yarn.lock index ef7d8021ca9..031b2001f93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -908,10 +908,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8" integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw== -"@gitlab/ui@29.27.0": - version "29.27.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-29.27.0.tgz#c5cbe1fee2164705ea6a431c85f6fdaa2ff7e166" - integrity sha512-VoiYrozWyE9l/ddX308vsu+wQqaJJN3csngIlrJit3DzVZV9Z/OVWU/X9CWV8m/ubyr5ysEMqnrtnsQClR9FiA== +"@gitlab/ui@29.28.0": + version "29.28.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-29.28.0.tgz#3444f6d26114f503d78b85fca67b5cc340a4a667" + integrity sha512-7jHqbnEy3P5J/G0/b+Nu+iw8XSOyTWLvyOEtNdFpBras1RxCE3C4AnPyGT7jei+WafTQN2Vzxz8VIgAxZ6PPvg== dependencies: "@babel/standalone" "^7.0.0" "@gitlab/vue-toasted" "^1.3.0" |