Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4c47bc5ec6
commit
84d72a5660
|
|
@ -121,6 +121,7 @@ review-stop:
|
|||
QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa"
|
||||
QA_CAN_TEST_GIT_PROTOCOL_V2: "false"
|
||||
QA_DEBUG: "true"
|
||||
QA_GENERATE_ALLURE_REPORT: "true"
|
||||
GITLAB_USERNAME: "root"
|
||||
GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
|
||||
GITLAB_ADMIN_USERNAME: "root"
|
||||
|
|
@ -141,6 +142,23 @@ review-stop:
|
|||
expire_in: 7 days
|
||||
when: always
|
||||
|
||||
.allure-report-base:
|
||||
image:
|
||||
name: ${GITLAB_DEPENDENCY_PROXY}andrcuns/allure-report-publisher:0.0.6
|
||||
entrypoint: [""]
|
||||
stage: post-qa
|
||||
variables:
|
||||
GIT_STRATEGY: none
|
||||
STORAGE_CREDENTIALS: $QA_ALLURE_REPORT_GCS_CREDENTIALS
|
||||
script:
|
||||
- |
|
||||
allure-report-publisher upload gcs \
|
||||
--results-glob="qa/gitlab-qa-run-*/**/allure-results/*" \
|
||||
--bucket="gitlab-qa-allure-reports" \
|
||||
--prefix="$ALLURE_REPORT_PATH_PREFIX/$CI_COMMIT_REF_SLUG" \
|
||||
--copy-latest \
|
||||
--color
|
||||
|
||||
review-qa-smoke:
|
||||
extends:
|
||||
- .review-qa-base
|
||||
|
|
@ -210,6 +228,22 @@ parallel-spec-reports:
|
|||
junit: qa/gitlab-qa-run-*/**/rspec-*.xml
|
||||
expire_in: 31d
|
||||
|
||||
allure-report-qa-smoke:
|
||||
extends:
|
||||
- .allure-report-base
|
||||
- .review:rules:review-qa-smoke
|
||||
needs: ["review-qa-smoke"]
|
||||
variables:
|
||||
ALLURE_REPORT_PATH_PREFIX: gitlab-review-smoke
|
||||
|
||||
allure-report-qa-all:
|
||||
extends:
|
||||
- .allure-report-base
|
||||
- .review:rules:review-qa-all
|
||||
needs: ["review-qa-all"]
|
||||
variables:
|
||||
ALLURE_REPORT_PATH_PREFIX: gitlab-review-all
|
||||
|
||||
danger-review:
|
||||
extends:
|
||||
- .default-retry
|
||||
|
|
|
|||
|
|
@ -1309,7 +1309,6 @@ RSpec/AnyInstanceOf:
|
|||
- 'spec/support/shared_examples/workers/authorized_projects_worker_shared_example.rb'
|
||||
- 'spec/support/shared_examples/workers/reactive_cacheable_shared_examples.rb'
|
||||
- 'spec/support/snowplow.rb'
|
||||
- 'spec/support/unicorn.rb'
|
||||
- 'spec/tasks/gitlab/cleanup_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/container_registry_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/db_rake_spec.rb'
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -305,7 +305,7 @@ gem 'gitlab-license', '~> 1.5'
|
|||
gem 'rack-attack', '~> 6.3.0'
|
||||
|
||||
# Sentry integration
|
||||
gem 'sentry-raven', '~> 3.0'
|
||||
gem 'sentry-raven', '~> 3.1'
|
||||
|
||||
# PostgreSQL query parsing
|
||||
gem 'pg_query', '~> 2.0.3'
|
||||
|
|
|
|||
|
|
@ -1170,7 +1170,7 @@ GEM
|
|||
selenium-webdriver (3.142.7)
|
||||
childprocess (>= 0.5, < 4.0)
|
||||
rubyzip (>= 1.2.2)
|
||||
sentry-raven (3.0.4)
|
||||
sentry-raven (3.1.2)
|
||||
faraday (>= 1.0)
|
||||
settingslogic (2.0.9)
|
||||
sexp_processor (4.15.1)
|
||||
|
|
@ -1620,7 +1620,7 @@ DEPENDENCIES
|
|||
sassc-rails (~> 2.1.0)
|
||||
seed-fu (~> 2.3.7)
|
||||
selenium-webdriver (~> 3.142)
|
||||
sentry-raven (~> 3.0)
|
||||
sentry-raven (~> 3.1)
|
||||
settingslogic (~> 2.0.9)
|
||||
shoulda-matchers (~> 4.0.1)
|
||||
sidekiq (~> 5.2.7)
|
||||
|
|
|
|||
|
|
@ -79,13 +79,20 @@ export default {
|
|||
<jobs-table-tabs @fetchJobsByStatus="fetchJobsByStatus" />
|
||||
|
||||
<div v-if="$apollo.loading" class="gl-mt-5">
|
||||
<gl-skeleton-loader
|
||||
preserve-aspect-ratio="none"
|
||||
equal-width-lines
|
||||
:lines="5"
|
||||
:width="600"
|
||||
:height="66"
|
||||
/>
|
||||
<gl-skeleton-loader :width="1248" :height="73">
|
||||
<circle cx="748.031" cy="37.7193" r="15.0307" />
|
||||
<circle cx="787.241" cy="37.7193" r="15.0307" />
|
||||
<circle cx="827.759" cy="37.7193" r="15.0307" />
|
||||
<circle cx="866.969" cy="37.7193" r="15.0307" />
|
||||
<circle cx="380" cy="37" r="18" />
|
||||
<rect x="432" y="19" width="126.587" height="15" />
|
||||
<rect x="432" y="41" width="247" height="15" />
|
||||
<rect x="158" y="19" width="86.1" height="15" />
|
||||
<rect x="158" y="41" width="168" height="15" />
|
||||
<rect x="22" y="19" width="96" height="36" />
|
||||
<rect x="924" y="30" width="96" height="15" />
|
||||
<rect x="1057" y="20" width="166" height="35" />
|
||||
</gl-skeleton-loader>
|
||||
</div>
|
||||
|
||||
<jobs-table-empty-state v-else-if="showEmptyState" />
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class IssuableFinder
|
|||
attr_reader :original_params
|
||||
attr_writer :parent
|
||||
|
||||
delegate(*%i[assignee milestones], to: :params)
|
||||
delegate(*%i[milestones], to: :params)
|
||||
|
||||
class << self
|
||||
def scalar_params
|
||||
|
|
@ -148,7 +148,6 @@ class IssuableFinder
|
|||
|
||||
# Negates all params found in `negatable_params`
|
||||
def filter_negated_items(items)
|
||||
items = by_negated_assignee(items)
|
||||
items = by_negated_label(items)
|
||||
items = by_negated_milestone(items)
|
||||
items = by_negated_release(items)
|
||||
|
|
@ -365,32 +364,21 @@ class IssuableFinder
|
|||
|
||||
def by_author(items)
|
||||
Issuables::AuthorFilter.new(
|
||||
items,
|
||||
params: original_params,
|
||||
or_filters_enabled: or_filters_enabled?
|
||||
).filter
|
||||
).filter(items)
|
||||
end
|
||||
|
||||
def by_assignee(items)
|
||||
if params.filter_by_no_assignee?
|
||||
items.unassigned
|
||||
elsif params.filter_by_any_assignee?
|
||||
items.assigned
|
||||
elsif params.assignee
|
||||
items.assigned_to(params.assignee)
|
||||
elsif params.assignee_id? || params.assignee_username? # assignee not found
|
||||
items.none
|
||||
else
|
||||
items
|
||||
end
|
||||
assignee_filter.filter(items)
|
||||
end
|
||||
|
||||
def by_negated_assignee(items)
|
||||
# We want CE users to be able to say "Issues not assigned to either PersonA nor PersonB"
|
||||
if not_params.assignees.present?
|
||||
items.not_assigned_to(not_params.assignees)
|
||||
else
|
||||
items
|
||||
def assignee_filter
|
||||
strong_memoize(:assignee_filter) do
|
||||
Issuables::AssigneeFilter.new(
|
||||
params: original_params,
|
||||
or_filters_enabled: or_filters_enabled?
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -27,14 +27,6 @@ class IssuableFinder
|
|||
params.present?
|
||||
end
|
||||
|
||||
def filter_by_no_assignee?
|
||||
params[:assignee_id].to_s.downcase == FILTER_NONE
|
||||
end
|
||||
|
||||
def filter_by_any_assignee?
|
||||
params[:assignee_id].to_s.downcase == FILTER_ANY
|
||||
end
|
||||
|
||||
def filter_by_no_label?
|
||||
downcased = label_names.map(&:downcase)
|
||||
|
||||
|
|
@ -156,24 +148,6 @@ class IssuableFinder
|
|||
end
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def assignees
|
||||
strong_memoize(:assignees) do
|
||||
if assignee_id?
|
||||
User.where(id: params[:assignee_id])
|
||||
elsif assignee_username?
|
||||
User.where(username: params[:assignee_username])
|
||||
else
|
||||
User.none
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def assignee
|
||||
assignees.first
|
||||
end
|
||||
|
||||
def label_names
|
||||
if labels?
|
||||
params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Issuables
|
||||
class AssigneeFilter < BaseFilter
|
||||
def filter(issuables)
|
||||
filtered = by_assignee(issuables)
|
||||
filtered = by_assignee_union(filtered)
|
||||
by_negated_assignee(filtered)
|
||||
end
|
||||
|
||||
def includes_user?(user)
|
||||
Array(params[:assignee_ids]).include?(user.id) ||
|
||||
Array(params[:assignee_id]).include?(user.id) ||
|
||||
Array(params[:assignee_username]).include?(user.username)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def by_assignee(issuables)
|
||||
if filter_by_no_assignee?
|
||||
issuables.unassigned
|
||||
elsif filter_by_any_assignee?
|
||||
issuables.assigned
|
||||
elsif has_assignee_param?(params)
|
||||
filter_by_assignees(issuables)
|
||||
else
|
||||
issuables
|
||||
end
|
||||
end
|
||||
|
||||
def by_assignee_union(issuables)
|
||||
return issuables unless or_filters_enabled? && has_assignee_param?(or_params)
|
||||
|
||||
issuables.assigned_to(assignee_ids(or_params))
|
||||
end
|
||||
|
||||
def by_negated_assignee(issuables)
|
||||
return issuables unless has_assignee_param?(not_params)
|
||||
|
||||
issuables.not_assigned_to(assignee_ids(not_params))
|
||||
end
|
||||
|
||||
def filter_by_no_assignee?
|
||||
params[:assignee_id].to_s.downcase == FILTER_NONE
|
||||
end
|
||||
|
||||
def filter_by_any_assignee?
|
||||
params[:assignee_id].to_s.downcase == FILTER_ANY
|
||||
end
|
||||
|
||||
def filter_by_assignees(issuables)
|
||||
assignee_ids = assignee_ids(params)
|
||||
|
||||
return issuables.none if assignee_ids.blank?
|
||||
|
||||
assignee_ids.each do |assignee_id|
|
||||
issuables = issuables.assigned_to(assignee_id)
|
||||
end
|
||||
|
||||
issuables
|
||||
end
|
||||
|
||||
def has_assignee_param?(specific_params)
|
||||
return if specific_params.nil?
|
||||
|
||||
specific_params[:assignee_ids].present? || specific_params[:assignee_id].present? || specific_params[:assignee_username].present?
|
||||
end
|
||||
|
||||
def assignee_ids(specific_params)
|
||||
if specific_params[:assignee_ids].present?
|
||||
Array(specific_params[:assignee_ids])
|
||||
elsif specific_params[:assignee_id].present?
|
||||
Array(specific_params[:assignee_id])
|
||||
elsif specific_params[:assignee_username].present?
|
||||
User.by_username(specific_params[:assignee_username]).select(:id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module Issuables
|
||||
class AuthorFilter < BaseFilter
|
||||
def filter
|
||||
def filter(issuables)
|
||||
filtered = by_author(issuables)
|
||||
filtered = by_author_union(filtered)
|
||||
by_negated_author(filtered)
|
||||
|
|
@ -21,7 +21,7 @@ module Issuables
|
|||
end
|
||||
|
||||
def by_author_union(issuables)
|
||||
return issuables unless or_filters_enabled? && or_params&.fetch(:author_username).present?
|
||||
return issuables unless or_filters_enabled? && or_params&.fetch(:author_username, false).present?
|
||||
|
||||
issuables.authored(User.by_username(or_params[:author_username]))
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
module Issuables
|
||||
class BaseFilter
|
||||
attr_reader :issuables, :params
|
||||
attr_reader :params
|
||||
|
||||
def initialize(issuables, params:, or_filters_enabled: false)
|
||||
@issuables = issuables
|
||||
FILTER_NONE = 'none'
|
||||
FILTER_ANY = 'any'
|
||||
|
||||
def initialize(params:, or_filters_enabled: false)
|
||||
@params = params
|
||||
@or_filters_enabled = or_filters_enabled
|
||||
end
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class IssuesFinder < IssuableFinder
|
|||
# can always see confidential issues assigned to them. This is just an
|
||||
# optimization since a very common usecase of this Finder is to load the
|
||||
# count of issues assigned to the user for the header bar.
|
||||
return Issue.all if current_user && params.assignees.include?(current_user)
|
||||
return Issue.all if current_user && assignee_filter.includes_user?(current_user)
|
||||
|
||||
return Issue.where('issues.confidential IS NOT TRUE') if params.user_cannot_see_confidential_issues?
|
||||
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ module IssuesHelper
|
|||
jira_integration_path: help_page_url('integration/jira/', anchor: 'view-jira-issues'),
|
||||
markdown_help_path: help_page_path('user/markdown'),
|
||||
max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes),
|
||||
new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.try(:id), milestone_id: finder.milestones.first.try(:id) }),
|
||||
new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.try(:id) }),
|
||||
project_import_jira_path: project_import_jira_path(project),
|
||||
project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json),
|
||||
project_milestones_path: project_milestones_path(project, format: :json),
|
||||
|
|
|
|||
|
|
@ -163,6 +163,8 @@ module Ci
|
|||
numericality: { greater_than_or_equal_to: 0.0,
|
||||
message: 'needs to be non-negative' }
|
||||
|
||||
validates :config, json_schema: { filename: 'ci_runner_config' }
|
||||
|
||||
# Searches for runners matching the given query.
|
||||
#
|
||||
# This method uses ILIKE on PostgreSQL.
|
||||
|
|
@ -353,7 +355,7 @@ module Ci
|
|||
end
|
||||
|
||||
def heartbeat(values)
|
||||
values = values&.slice(:version, :revision, :platform, :architecture, :ip_address) || {}
|
||||
values = values&.slice(:version, :revision, :platform, :architecture, :ip_address, :config) || {}
|
||||
values[:contacted_at] = Time.current
|
||||
|
||||
cache_attributes(values)
|
||||
|
|
|
|||
|
|
@ -101,21 +101,20 @@ module Issuable
|
|||
scope :unassigned, -> do
|
||||
where("NOT EXISTS (SELECT TRUE FROM #{to_ability_name}_assignees WHERE #{to_ability_name}_id = #{to_ability_name}s.id)")
|
||||
end
|
||||
scope :assigned_to, ->(u) do
|
||||
assignees_table = Arel::Table.new("#{to_ability_name}_assignees")
|
||||
sql = assignees_table.project('true').where(assignees_table[:user_id].in(u.id)).where(Arel::Nodes::SqlLiteral.new("#{to_ability_name}_id = #{to_ability_name}s.id"))
|
||||
where("EXISTS (#{sql.to_sql})")
|
||||
scope :assigned_to, ->(users) do
|
||||
assignees_class = self.reflect_on_association("#{to_ability_name}_assignees").klass
|
||||
|
||||
condition = assignees_class.where(user_id: users).where(Arel.sql("#{to_ability_name}_id = #{to_ability_name}s.id"))
|
||||
where(condition.arel.exists)
|
||||
end
|
||||
scope :not_assigned_to, ->(users) do
|
||||
assignees_class = self.reflect_on_association("#{to_ability_name}_assignees").klass
|
||||
|
||||
condition = assignees_class.where(user_id: users).where(Arel.sql("#{to_ability_name}_id = #{to_ability_name}s.id"))
|
||||
where(condition.arel.exists.not)
|
||||
end
|
||||
# rubocop:enable GitlabSecurity/SqlInjection
|
||||
|
||||
scope :not_assigned_to, ->(users) do
|
||||
assignees_table = Arel::Table.new("#{to_ability_name}_assignees")
|
||||
sql = assignees_table.project('true')
|
||||
.where(assignees_table[:user_id].in(users))
|
||||
.where(Arel::Nodes::SqlLiteral.new("#{to_ability_name}_id = #{to_ability_name}s.id"))
|
||||
where(sql.exists.not)
|
||||
end
|
||||
|
||||
scope :without_particular_labels, ->(label_names) do
|
||||
labels_table = Label.arel_table
|
||||
label_links_table = LabelLink.arel_table
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ class GroupMember < Member
|
|||
|
||||
belongs_to :group, foreign_key: 'source_id'
|
||||
alias_attribute :namespace_id, :source_id
|
||||
delegate :update_two_factor_requirement, to: :user
|
||||
delegate :update_two_factor_requirement, to: :user, allow_nil: true
|
||||
|
||||
# Make sure group member points only to group as it source
|
||||
default_value_for :source_type, SOURCE_TYPE
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class MergeRequest < ApplicationRecord
|
|||
SORTING_PREFERENCE_FIELD = :merge_requests_sort
|
||||
|
||||
ALLOWED_TO_USE_MERGE_BASE_PIPELINE_FOR_COMPARISON = {
|
||||
'Ci::CompareMetricsReportsService' => ->(project) { ::Gitlab::Ci::Features.merge_base_pipeline_for_metrics_comparison?(project) },
|
||||
'Ci::CompareMetricsReportsService' => ->(project) { true },
|
||||
'Ci::CompareCodequalityReportsService' => ->(project) { true }
|
||||
}.freeze
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ module DesignManagement
|
|||
def with_temporary_branch(&block)
|
||||
target_repository.create_if_not_exists
|
||||
|
||||
create_master_branch! if target_repository.empty?
|
||||
create_default_branch! if target_repository.empty?
|
||||
create_temporary_branch!
|
||||
|
||||
yield
|
||||
|
|
@ -95,9 +95,9 @@ module DesignManagement
|
|||
end
|
||||
|
||||
# A project that does not have any designs will have a blank design
|
||||
# repository. To create a temporary branch from `master` we need
|
||||
# create `master` first by adding a file to it.
|
||||
def create_master_branch!
|
||||
# repository. To create a temporary branch from default branch we need to
|
||||
# create default branch first by adding a file to it.
|
||||
def create_default_branch!
|
||||
target_repository.create_file(
|
||||
git_user,
|
||||
".CopyDesignCollectionService_#{Time.now.to_i}",
|
||||
|
|
@ -121,7 +121,7 @@ module DesignManagement
|
|||
target_repository.rm_branch(git_user, temporary_branch)
|
||||
end
|
||||
|
||||
# Merge the temporary branch containing the commits to `master`
|
||||
# Merge the temporary branch containing the commits to default branch
|
||||
# and update the state of the target_design_collection.
|
||||
def finalize!
|
||||
source_sha = shas.last
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ module DesignManagement
|
|||
attr_reader :issue
|
||||
|
||||
def target_branch
|
||||
repository.root_ref || "master"
|
||||
repository.root_ref || Gitlab::DefaultBranch.value(object: project)
|
||||
end
|
||||
|
||||
def collection
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "CI Runner config values",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"gpus": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
%td{ style: "#{default_style}border-top:1px solid #ededed;" }
|
||||
= _('Time')
|
||||
%td{ style: "#{default_style}color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
|
||||
= @time.strftime('%Y-%m-%d %l:%M:%S %p %Z')
|
||||
= @time.strftime('%Y-%m-%d %k:%M:%S %Z')
|
||||
%tr.spacer
|
||||
%td{ style: spacer_style }
|
||||
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@
|
|||
|
||||
|
||||
.js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') }
|
||||
= form_for @project, html: { class: 'new_project gl-show-field-errors' } do |f|
|
||||
= form_for @project, html: { class: 'new_project' } do |f|
|
||||
%hr
|
||||
= render "shared/import_form", f: f
|
||||
= render 'projects/new_project_fields', f: f, project_name_id: "import-url-name", hide_init_with_readme: true, track_label: track_label
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@
|
|||
= button_tag _("Edit issues"), class: "gl-button btn btn-default gl-mr-3 js-bulk-update-toggle"
|
||||
- if show_new_issue_link?(@project)
|
||||
= link_to _("New issue"), new_project_issue_path(@project,
|
||||
issue: { assignee_id: finder.assignee.try(:id),
|
||||
milestone_id: finder.milestones.first.try(:id) }),
|
||||
issue: { milestone_id: finder.milestones.first.try(:id) }),
|
||||
class: "gl-button btn btn-confirm",
|
||||
id: "new_issue_link"
|
||||
|
||||
|
|
|
|||
|
|
@ -6,14 +6,8 @@
|
|||
= f.label :import_url, class: 'label-bold' do
|
||||
%span
|
||||
= _('Git repository URL')
|
||||
= f.text_field :import_url,
|
||||
value: import_url.sanitized_url,
|
||||
autocomplete: 'off',
|
||||
class: 'form-control gl-form-input',
|
||||
placeholder: 'https://gitlab.company.com/group/project.git',
|
||||
required: true,
|
||||
pattern: '(?:git|https?):\/\/.*/.*\.git$',
|
||||
title: _('Please provide a valid URL ending with .git')
|
||||
= f.text_field :import_url, value: import_url.sanitized_url,
|
||||
autocomplete: 'off', class: 'form-control gl-form-input', placeholder: 'https://gitlab.company.com/group/project.git', required: true
|
||||
|
||||
.row
|
||||
.form-group.col-md-6
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: merge_base_pipeline_for_metrics_comparison
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61282
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/330809
|
||||
milestone: '13.12'
|
||||
type: development
|
||||
group: group::testing
|
||||
default_enabled: false
|
||||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
if ENV['ENABLE_RBTRACE']
|
||||
Gitlab::Cluster::LifecycleEvents.on_worker_start do
|
||||
# Unicorn clears out signals before it forks, so rbtrace won't work
|
||||
# unless it is enabled after the fork.
|
||||
# We need to require `rbtrace` in a context of a worker process.
|
||||
# See https://github.com/tmm1/rbtrace/issues/56#issuecomment-648683596.
|
||||
require 'rbtrace'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,144 +0,0 @@
|
|||
# Sample verbose configuration file for Unicorn (not Rack)
|
||||
#
|
||||
# This configuration file documents many features of Unicorn
|
||||
# that may not be needed for some applications. See
|
||||
# http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
|
||||
# for a much simpler configuration file.
|
||||
#
|
||||
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
|
||||
# documentation.
|
||||
|
||||
# Note: If you change this file in a merge request, please also create a
|
||||
# merge request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
|
||||
|
||||
# Relative URL support
|
||||
# WARNING: We recommend using an FQDN to host GitLab in a root path instead
|
||||
# of using a relative URL.
|
||||
# Documentation: http://doc.gitlab.com/ce/install/relative_url.html
|
||||
# Uncomment and customize the following line to run in a non-root path
|
||||
#
|
||||
# ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
|
||||
|
||||
# Read about unicorn workers here:
|
||||
# http://doc.gitlab.com/ee/install/requirements.html#unicorn-workers
|
||||
#
|
||||
worker_processes 3
|
||||
|
||||
# Since Unicorn is never exposed to outside clients, it does not need to
|
||||
# run on the standard HTTP port (80), there is no reason to start Unicorn
|
||||
# as root unless it's from system init scripts.
|
||||
# If running the master process as root and the workers as an unprivileged
|
||||
# user, do this to switch euid/egid in the workers (also chowns logs):
|
||||
# user "unprivileged_user", "unprivileged_group"
|
||||
|
||||
# Help ensure your application will always spawn in the symlinked
|
||||
# "current" directory that Capistrano sets up.
|
||||
working_directory "/home/git/gitlab" # available in 0.94.0+
|
||||
|
||||
# Listen on both a Unix domain socket and a TCP port.
|
||||
# If you are load-balancing multiple Unicorn masters, lower the backlog
|
||||
# setting to e.g. 64 for faster failover.
|
||||
listen "/home/git/gitlab/tmp/sockets/gitlab.socket", :backlog => 1024
|
||||
listen "127.0.0.1:8080", :tcp_nopush => true
|
||||
|
||||
# destroy workers after 30 seconds instead of 60 seconds (the default)
|
||||
#
|
||||
# NOTICE: git push over http depends on this value.
|
||||
# If you want to be able to push huge amount of data to git repository over http
|
||||
# you will have to increase this value too.
|
||||
#
|
||||
# Example of output if you try to push 1GB repo to GitLab over http.
|
||||
# -> git push http://gitlab.... master
|
||||
#
|
||||
# error: RPC failed; result=18, HTTP code = 200
|
||||
# fatal: The remote end hung up unexpectedly
|
||||
# fatal: The remote end hung up unexpectedly
|
||||
#
|
||||
# For more information see http://stackoverflow.com/a/21682112/752049
|
||||
#
|
||||
timeout 60
|
||||
|
||||
# feel free to point this anywhere accessible on the filesystem
|
||||
pid "/home/git/gitlab/tmp/pids/unicorn.pid"
|
||||
|
||||
# By default, the Unicorn logger will write to stderr.
|
||||
# Additionally, some applications/frameworks log to stderr or stdout,
|
||||
# so prevent them from going to /dev/null when daemonized here:
|
||||
stderr_path "/home/git/gitlab/log/unicorn.stderr.log"
|
||||
stdout_path "/home/git/gitlab/log/unicorn.stdout.log"
|
||||
|
||||
# Save memory by sharing the application code among multiple Unicorn workers
|
||||
# with "preload_app true". See:
|
||||
# https://www.rubydoc.info/gems/unicorn/5.1.0/Unicorn%2FConfigurator:preload_app
|
||||
# https://brandur.org/ruby-memory#copy-on-write
|
||||
preload_app true
|
||||
|
||||
# Enable this flag to have unicorn test client connections by writing the
|
||||
# beginning of the HTTP headers before calling the application. This
|
||||
# prevents calling the application for connections that have disconnected
|
||||
# while queued. This is only guaranteed to detect clients on the same
|
||||
# host unicorn runs on, and unlikely to detect disconnects even on a
|
||||
# fast LAN.
|
||||
check_client_connection false
|
||||
|
||||
require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events"
|
||||
require_relative "/home/git/gitlab/lib/gitlab/log_timestamp_formatter.rb"
|
||||
|
||||
before_exec do |server|
|
||||
# Signal application hooks that we're about to restart
|
||||
Gitlab::Cluster::LifecycleEvents.do_before_master_restart
|
||||
end
|
||||
|
||||
run_once = true
|
||||
|
||||
before_fork do |server, worker|
|
||||
if run_once
|
||||
# There is a difference between Puma and Unicorn:
|
||||
# - Puma calls before_fork once when booting up master process
|
||||
# - Unicorn runs before_fork whenever new work is spawned
|
||||
# To unify this behavior we call before_fork only once (we use
|
||||
# this callback for deleting Prometheus files so for our purposes
|
||||
# it makes sense to align behavior with Puma)
|
||||
run_once = false
|
||||
|
||||
# Signal application hooks that we're about to fork
|
||||
Gitlab::Cluster::LifecycleEvents.do_before_fork
|
||||
end
|
||||
|
||||
# The following is only recommended for memory/DB-constrained
|
||||
# installations. It is not needed if your system can house
|
||||
# twice as many worker_processes as you have configured.
|
||||
#
|
||||
# This allows a new master process to incrementally
|
||||
# phase out the old master process with SIGTTOU to avoid a
|
||||
# thundering herd (especially in the "preload_app false" case)
|
||||
# when doing a transparent upgrade. The last worker spawned
|
||||
# will then kill off the old master process with a SIGQUIT.
|
||||
old_pid = "#{server.config[:pid]}.oldbin"
|
||||
if old_pid != server.pid
|
||||
begin
|
||||
sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
|
||||
Process.kill(sig, File.read(old_pid).to_i)
|
||||
rescue Errno::ENOENT, Errno::ESRCH
|
||||
end
|
||||
end
|
||||
#
|
||||
# Throttle the master from forking too quickly by sleeping. Due
|
||||
# to the implementation of standard Unix signal handlers, this
|
||||
# helps (but does not completely) prevent identical, repeated signals
|
||||
# from being lost when the receiving process is busy.
|
||||
# sleep 1
|
||||
end
|
||||
|
||||
after_fork do |server, worker|
|
||||
# Signal application hooks of worker start
|
||||
Gitlab::Cluster::LifecycleEvents.do_worker_start
|
||||
|
||||
# per-process listener ports for debugging/admin/migrations
|
||||
# addr = "127.0.0.1:#{9293 + worker.nr}"
|
||||
# server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
|
||||
end
|
||||
|
||||
# Configure the default logger to use a custom formatter that formats the
|
||||
# timestamps to be in UTC and in ISO8601.3 format
|
||||
Configurator::DEFAULTS[:logger].formatter = Gitlab::LogTimestampFormatter.new
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# This file is used by the GDK to generate a default config/unicorn.rb file
|
||||
# Note that `/home/git` will be substituted for the actual GDK root
|
||||
# directory when this file is generated
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
worker_processes 2
|
||||
timeout 60
|
||||
|
||||
listen '/home/git/gitlab.socket'
|
||||
|
||||
preload_app true
|
||||
check_client_connection false
|
||||
|
||||
require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events"
|
||||
require_relative "/home/git/gitlab/lib/gitlab/log_timestamp_formatter.rb"
|
||||
|
||||
before_exec do |server|
|
||||
# Signal application hooks that we're about to restart
|
||||
Gitlab::Cluster::LifecycleEvents.do_before_master_restart
|
||||
end
|
||||
|
||||
run_once = true
|
||||
|
||||
before_fork do |server, worker|
|
||||
if run_once
|
||||
# There is a difference between Puma and Unicorn:
|
||||
# - Puma calls before_fork once when booting up master process
|
||||
# - Unicorn runs before_fork whenever new work is spawned
|
||||
# To unify this behavior we call before_fork only once (we use
|
||||
# this callback for deleting Prometheus files so for our purposes
|
||||
# it makes sense to align behavior with Puma)
|
||||
run_once = false
|
||||
|
||||
# Signal application hooks that we're about to fork
|
||||
Gitlab::Cluster::LifecycleEvents.do_before_fork
|
||||
end
|
||||
|
||||
# The following is only recommended for memory/DB-constrained
|
||||
# installations. It is not needed if your system can house
|
||||
# twice as many worker_processes as you have configured.
|
||||
#
|
||||
# This allows a new master process to incrementally
|
||||
# phase out the old master process with SIGTTOU to avoid a
|
||||
# thundering herd (especially in the "preload_app false" case)
|
||||
# when doing a transparent upgrade. The last worker spawned
|
||||
# will then kill off the old master process with a SIGQUIT.
|
||||
old_pid = "#{server.config[:pid]}.oldbin"
|
||||
if old_pid != server.pid
|
||||
begin
|
||||
sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
|
||||
Process.kill(sig, File.read(old_pid).to_i)
|
||||
rescue Errno::ENOENT, Errno::ESRCH
|
||||
end
|
||||
end
|
||||
#
|
||||
# Throttle the master from forking too quickly by sleeping. Due
|
||||
# to the implementation of standard Unix signal handlers, this
|
||||
# helps (but does not completely) prevent identical, repeated signals
|
||||
# from being lost when the receiving process is busy.
|
||||
# sleep 1
|
||||
end
|
||||
|
||||
after_fork do |server, worker|
|
||||
# Signal application hooks of worker start
|
||||
Gitlab::Cluster::LifecycleEvents.do_worker_start
|
||||
|
||||
# per-process listener ports for debugging/admin/migrations
|
||||
# addr = "127.0.0.1:#{9293 + worker.nr}"
|
||||
# server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
|
||||
end
|
||||
|
||||
# Configure the default logger to use a custom formatter that formats the
|
||||
# timestamps to be in UTC and in ISO8601.3 format
|
||||
Configurator::DEFAULTS[:logger].formatter = Gitlab::LogTimestampFormatter.new
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddConfigToCiRunners < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :ci_runners, :config, :jsonb, default: {}, null: false
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
0bd47f9055aab927a4e8efb4f995f44532880926af9892af60f7d2b8dcdef4a6
|
||||
|
|
@ -11148,7 +11148,8 @@ CREATE TABLE ci_runners (
|
|||
runner_type smallint NOT NULL,
|
||||
token_encrypted character varying,
|
||||
public_projects_minutes_cost_factor double precision DEFAULT 0.0 NOT NULL,
|
||||
private_projects_minutes_cost_factor double precision DEFAULT 1.0 NOT NULL
|
||||
private_projects_minutes_cost_factor double precision DEFAULT 1.0 NOT NULL,
|
||||
config jsonb DEFAULT '{}'::jsonb NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE ci_runners_id_seq
|
||||
|
|
|
|||
|
|
@ -154,6 +154,8 @@ To change the namespace linked to a subscription:
|
|||
|
||||
Subscription charges are calculated based on the total number of users in a group, including its subgroups and nested projects. If the total number of users exceeds the number of seats in your subscription, your account is charged for the additional users.
|
||||
|
||||
Only one namespace can be linked to a subscription.
|
||||
|
||||
### Change Customers Portal account password
|
||||
|
||||
To change the password for this customers portal account:
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
|
|
@ -30,4 +30,4 @@ for a notification email to be sent.
|
|||
|
||||
## Example email
|
||||
|
||||

|
||||

|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ module API
|
|||
optional :architecture, type: String, desc: %q(Runner's architecture)
|
||||
optional :executor, type: String, desc: %q(Runner's executor)
|
||||
optional :features, type: Hash, desc: %q(Runner's features)
|
||||
optional :config, type: Hash, desc: %q(Runner's config) do
|
||||
optional :gpus, type: String, desc: %q(GPUs enabled)
|
||||
end
|
||||
end
|
||||
optional :session, type: Hash, desc: %q(Runner's session data) do
|
||||
optional :url, type: String, desc: %q(Session's url)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ module API
|
|||
return get_runner_ip unless params['info'].present?
|
||||
|
||||
attributes_for_keys(%w(name version revision platform architecture), params['info'])
|
||||
.merge(get_runner_config_from_request)
|
||||
.merge(get_runner_ip)
|
||||
end
|
||||
|
||||
|
|
@ -91,6 +92,12 @@ module API
|
|||
def track_ci_minutes_usage!(_build, _runner)
|
||||
# noop: overridden in EE
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_runner_config_from_request
|
||||
{ config: attributes_for_keys(%w(gpus), params.dig('info', 'config')) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,10 +18,6 @@ module Gitlab
|
|||
Feature.enabled?(:ci_pipeline_status_omit_commit_sha_in_cache_key, project, default_enabled: true)
|
||||
end
|
||||
|
||||
def self.merge_base_pipeline_for_metrics_comparison?(project)
|
||||
Feature.enabled?(:merge_base_pipeline_for_metrics_comparison, project, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
# NOTE: The feature flag `disallow_to_create_merge_request_pipelines_in_target_project`
|
||||
# is a safe switch to disable the feature for a particular project when something went wrong,
|
||||
# therefore it's not supposed to be enabled by default.
|
||||
|
|
|
|||
|
|
@ -162,9 +162,6 @@ module Gitlab
|
|||
# Sidekiq doesn't fork
|
||||
return false if Gitlab::Runtime.sidekiq?
|
||||
|
||||
# Unicorn always forks
|
||||
return true if Gitlab::Runtime.unicorn?
|
||||
|
||||
# Puma sometimes forks
|
||||
return true if in_clustered_puma?
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@ module Gitlab
|
|||
:rails_runner,
|
||||
:rake,
|
||||
:sidekiq,
|
||||
:test_suite,
|
||||
:unicorn
|
||||
:test_suite
|
||||
].freeze
|
||||
|
||||
class << self
|
||||
|
|
@ -36,11 +35,6 @@ module Gitlab
|
|||
!!defined?(::Puma)
|
||||
end
|
||||
|
||||
# For unicorn, we need to check for actual server instances to avoid false positives.
|
||||
def unicorn?
|
||||
!!(defined?(::Unicorn) && defined?(::Unicorn::HttpServer))
|
||||
end
|
||||
|
||||
def sidekiq?
|
||||
!!(defined?(::Sidekiq) && Sidekiq.server?)
|
||||
end
|
||||
|
|
@ -66,7 +60,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def web_server?
|
||||
puma? || unicorn?
|
||||
puma?
|
||||
end
|
||||
|
||||
def action_cable?
|
||||
|
|
|
|||
|
|
@ -24645,9 +24645,6 @@ msgstr ""
|
|||
msgid "Please provide a valid URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Please provide a valid URL ending with .git"
|
||||
msgstr ""
|
||||
|
||||
msgid "Please provide a valid URL."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@ tmp/
|
|||
.tool-versions
|
||||
.ruby-gemset
|
||||
urls.yml
|
||||
reports/
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ source 'https://rubygems.org'
|
|||
|
||||
gem 'gitlab-qa'
|
||||
gem 'activesupport', '~> 6.0.3.3' # This should stay in sync with the root's Gemfile
|
||||
gem 'allure-rspec', '~> 2.13.10'
|
||||
gem 'capybara', '~> 3.29.0'
|
||||
gem 'capybara-screenshot', '~> 1.0.23'
|
||||
gem 'rake', '~> 12.3.3'
|
||||
|
|
|
|||
|
|
@ -19,6 +19,14 @@ GEM
|
|||
rack-test (>= 1.1.0, < 2.0)
|
||||
rest-client (>= 2.0.2, < 3.0)
|
||||
rspec (~> 3.8)
|
||||
allure-rspec (2.13.10)
|
||||
allure-ruby-commons (= 2.13.10)
|
||||
rspec-core (>= 3.8, < 4)
|
||||
allure-ruby-commons (2.13.10)
|
||||
mime-types (>= 3.3, < 4)
|
||||
oj (>= 3.10, < 4)
|
||||
require_all (>= 2, < 4)
|
||||
uuid (>= 2.3, < 3)
|
||||
ast (2.4.1)
|
||||
binding_ninja (0.2.3)
|
||||
byebug (9.1.0)
|
||||
|
|
@ -74,6 +82,8 @@ GEM
|
|||
rake
|
||||
launchy (2.4.3)
|
||||
addressable (~> 2.3)
|
||||
macaddr (1.7.2)
|
||||
systemu (~> 2.6.5)
|
||||
memoizable (0.4.2)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
method_source (0.9.0)
|
||||
|
|
@ -96,6 +106,7 @@ GEM
|
|||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 3)
|
||||
oj (3.11.5)
|
||||
parallel (1.19.2)
|
||||
parallel_tests (2.29.0)
|
||||
parallel
|
||||
|
|
@ -119,6 +130,7 @@ GEM
|
|||
rack (>= 1.0, < 3)
|
||||
rake (12.3.3)
|
||||
regexp_parser (1.6.0)
|
||||
require_all (3.0.0)
|
||||
rest-client (2.1.0)
|
||||
http-accept (>= 1.7.0, < 2.0)
|
||||
http-cookie (>= 1.0.2, < 2.0)
|
||||
|
|
@ -154,6 +166,7 @@ GEM
|
|||
selenium-webdriver (3.142.6)
|
||||
childprocess (>= 0.5, < 4.0)
|
||||
rubyzip (>= 1.2.2)
|
||||
systemu (2.6.5)
|
||||
thread_safe (0.3.6)
|
||||
timecop (0.9.1)
|
||||
tzinfo (1.2.9)
|
||||
|
|
@ -169,6 +182,8 @@ GEM
|
|||
equalizer (~> 0.0.9)
|
||||
parser (>= 2.6.5)
|
||||
procto (~> 0.0.2)
|
||||
uuid (2.3.9)
|
||||
macaddr (~> 1.0)
|
||||
watir (6.18.0)
|
||||
regexp_parser (>= 1.2, < 3)
|
||||
selenium-webdriver (>= 3.8)
|
||||
|
|
@ -182,6 +197,7 @@ PLATFORMS
|
|||
DEPENDENCIES
|
||||
activesupport (~> 6.0.3.3)
|
||||
airborne (~> 0.3.4)
|
||||
allure-rspec (~> 2.13.10)
|
||||
capybara (~> 3.29.0)
|
||||
capybara-screenshot (~> 1.0.23)
|
||||
chemlab (~> 0.5)
|
||||
|
|
|
|||
1
qa/qa.rb
1
qa/qa.rb
|
|
@ -45,6 +45,7 @@ module QA
|
|||
autoload :IPAddress, 'qa/runtime/ip_address'
|
||||
autoload :Search, 'qa/runtime/search'
|
||||
autoload :ApplicationSettings, 'qa/runtime/application_settings'
|
||||
autoload :AllureReport, 'qa/runtime/allure_report'
|
||||
|
||||
module API
|
||||
autoload :Client, 'qa/runtime/api/client'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Runtime
|
||||
class AllureReport
|
||||
class << self
|
||||
# Configure allure reports
|
||||
#
|
||||
# @return [void]
|
||||
def configure!
|
||||
return unless Env.generate_allure_report?
|
||||
|
||||
require 'allure-rspec'
|
||||
|
||||
configure_allure
|
||||
configure_attachments
|
||||
configure_rspec
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Configure allure reporter
|
||||
#
|
||||
# @return [void]
|
||||
def configure_allure
|
||||
AllureRspec.configure do |config|
|
||||
config.results_directory = 'tmp/allure-results'
|
||||
config.clean_results_directory = true
|
||||
end
|
||||
end
|
||||
|
||||
# Set up failure screenshot attachments
|
||||
#
|
||||
# @return [void]
|
||||
def configure_attachments
|
||||
Capybara::Screenshot.after_save_screenshot do |path|
|
||||
Allure.add_attachment(
|
||||
name: 'screenshot',
|
||||
source: File.open(path),
|
||||
type: Allure::ContentType::PNG,
|
||||
test_case: true
|
||||
)
|
||||
end
|
||||
Capybara::Screenshot.after_save_html do |path|
|
||||
Allure.add_attachment(
|
||||
name: 'html',
|
||||
source: File.open(path),
|
||||
type: 'text/html',
|
||||
test_case: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Configure rspec
|
||||
#
|
||||
# @return [void]
|
||||
def configure_rspec
|
||||
RSpec.configure do |config|
|
||||
config.formatter = AllureRspecFormatter
|
||||
|
||||
config.before do |example|
|
||||
next if example.attempts && example.attempts > 0
|
||||
|
||||
testcase = example.metadata[:testcase]
|
||||
example.tms('Testcase', testcase) if testcase
|
||||
|
||||
issue = example.metadata.dig(:quarantine, :issue)
|
||||
example.issue('Issue', issue) if issue
|
||||
|
||||
example.add_link(name: "Job(#{ENV['CI_JOB_NAME']})", url: ENV['CI_JOB_URL']) if ENV['CI']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
require 'gitlab/qa'
|
||||
require 'uri'
|
||||
require 'active_support/core_ext/object/blank'
|
||||
|
||||
module QA
|
||||
module Runtime
|
||||
|
|
@ -53,6 +52,10 @@ module QA
|
|||
enabled?(ENV['QA_DEBUG'], default: false)
|
||||
end
|
||||
|
||||
def generate_allure_report?
|
||||
enabled?(ENV['QA_GENERATE_ALLURE_REPORT'], default: false)
|
||||
end
|
||||
|
||||
def default_branch
|
||||
ENV['QA_DEFAULT_BRANCH'] || 'master'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19,15 +19,15 @@ module QA
|
|||
|
||||
define_singleton_method(attribute) do
|
||||
attributes[attribute.to_sym].tap do |value|
|
||||
if value.to_s.empty?
|
||||
raise ArgumentError, "Empty `#{attribute}` attribute!"
|
||||
end
|
||||
raise ArgumentError, "Empty `#{attribute}` attribute!" if value.to_s.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def from_env(var)
|
||||
JSON.parse(Runtime::Env.runtime_scenario_attributes).each { |k, v| define(k, v) }
|
||||
return if var.blank?
|
||||
|
||||
JSON.parse(var).each { |k, v| define(k, v) }
|
||||
end
|
||||
|
||||
def method_missing(name, *)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ require_relative '../qa'
|
|||
require 'rspec/retry'
|
||||
require 'rspec-parameterized'
|
||||
require 'active_support/core_ext/hash'
|
||||
require 'active_support/core_ext/object/blank'
|
||||
|
||||
if ENV['CI'] && QA::Runtime::Env.knapsack? && !ENV['NO_KNAPSACK']
|
||||
require 'knapsack'
|
||||
|
|
@ -11,8 +12,8 @@ if ENV['CI'] && QA::Runtime::Env.knapsack? && !ENV['NO_KNAPSACK']
|
|||
end
|
||||
|
||||
QA::Runtime::Browser.configure!
|
||||
|
||||
QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes) if QA::Runtime::Env.runtime_scenario_attributes
|
||||
QA::Runtime::AllureReport.configure!
|
||||
QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes)
|
||||
|
||||
Dir[::File.join(__dir__, "support/helpers/*.rb")].sort.each { |f| require f }
|
||||
Dir[::File.join(__dir__, "support/matchers/*.rb")].sort.each { |f| require f }
|
||||
|
|
|
|||
|
|
@ -356,16 +356,6 @@ RSpec.describe 'New project', :js do
|
|||
expect(git_import_instructions).to have_content 'Git repository URL'
|
||||
end
|
||||
|
||||
it 'reports error if repo URL does not end with .git' do
|
||||
fill_in 'project_import_url', with: 'http://foo/bar'
|
||||
fill_in 'project_name', with: 'import-project-without-git-suffix'
|
||||
fill_in 'project_path', with: 'import-project-without-git-suffix'
|
||||
|
||||
click_button 'Create project'
|
||||
|
||||
expect(page).to have_text('Please provide a valid URL ending with .git')
|
||||
end
|
||||
|
||||
it 'keeps "Import project" tab open after form validation error' do
|
||||
collision_project = create(:project, name: 'test-name-collision', namespace: user.namespace)
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,11 @@ RSpec.describe IssuesFinder do
|
|||
let(:expected_issuables) { [issue3, issue4] }
|
||||
end
|
||||
|
||||
it_behaves_like 'assignee OR filter' do
|
||||
let(:params) { { or: { assignee_id: [user.id, user2.id] } } }
|
||||
let(:expected_issuables) { [issue1, issue2, issue3, issue5] }
|
||||
end
|
||||
|
||||
context 'when assignee_id does not exist' do
|
||||
it_behaves_like 'assignee NOT ID filter' do
|
||||
let(:params) { { not: { assignee_id: -100 } } }
|
||||
|
|
@ -79,6 +84,11 @@ RSpec.describe IssuesFinder do
|
|||
let(:expected_issuables) { [issue3, issue4] }
|
||||
end
|
||||
|
||||
it_behaves_like 'assignee OR filter' do
|
||||
let(:params) { { or: { assignee_username: [user2.username, user3.username] } } }
|
||||
let(:expected_issuables) { [issue2, issue3] }
|
||||
end
|
||||
|
||||
context 'when assignee_username does not exist' do
|
||||
it_behaves_like 'assignee NOT username filter' do
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ RSpec.describe IssuesHelper do
|
|||
jira_integration_path: help_page_url('integration/jira/', anchor: 'view-jira-issues'),
|
||||
markdown_help_path: help_page_path('user/markdown'),
|
||||
max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes),
|
||||
new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.id, milestone_id: finder.milestones.first.id }),
|
||||
new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.id }),
|
||||
project_import_jira_path: project_import_jira_path(project),
|
||||
project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json),
|
||||
project_milestones_path: project_milestones_path(project, format: :json),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::Helpers::Runner do
|
||||
let(:ip_address) { '1.2.3.4' }
|
||||
let(:runner_class) do
|
||||
Class.new do
|
||||
include API::Helpers
|
||||
include API::Helpers::Runner
|
||||
|
||||
attr_accessor :params
|
||||
|
||||
def initialize(params)
|
||||
@params = params
|
||||
end
|
||||
|
||||
def ip_address
|
||||
'1.2.3.4'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
let(:runner_helper) { runner_class.new(runner_params) }
|
||||
|
||||
describe '#get_runner_details_from_request' do
|
||||
context 'when no runner info is present' do
|
||||
let(:runner_params) { {} }
|
||||
|
||||
it 'returns the runner IP' do
|
||||
expect(runner_helper.get_runner_details_from_request).to eq({ ip_address: ip_address })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when runner info is present' do
|
||||
let(:name) { 'runner' }
|
||||
let(:version) { '1.2.3' }
|
||||
let(:revision) { '10.0' }
|
||||
let(:platform) { 'test' }
|
||||
let(:architecture) { 'arm' }
|
||||
let(:config) { { 'gpus' => 'all' } }
|
||||
let(:runner_params) do
|
||||
{
|
||||
'info' =>
|
||||
{
|
||||
'name' => name,
|
||||
'version' => version,
|
||||
'revision' => revision,
|
||||
'platform' => platform,
|
||||
'architecture' => architecture,
|
||||
'config' => config,
|
||||
'ignored' => 1
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
subject(:details) { runner_helper.get_runner_details_from_request }
|
||||
|
||||
it 'extracts the runner details', :aggregate_failures do
|
||||
expect(details.keys).to match_array(%w(name version revision platform architecture config ip_address))
|
||||
expect(details['name']).to eq(name)
|
||||
expect(details['version']).to eq(version)
|
||||
expect(details['revision']).to eq(revision)
|
||||
expect(details['platform']).to eq(platform)
|
||||
expect(details['architecture']).to eq(architecture)
|
||||
expect(details['config']).to eq(config)
|
||||
expect(details['ip_address']).to eq(ip_address)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -4,7 +4,15 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Gitlab::ErrorTracking::Processor::ContextPayloadProcessor do
|
||||
describe '.call' do
|
||||
let(:event) { Raven::Event.new(payload) }
|
||||
let(:required_options) do
|
||||
{
|
||||
configuration: Raven.configuration,
|
||||
context: Raven.context,
|
||||
breadcrumbs: Raven.breadcrumbs
|
||||
}
|
||||
end
|
||||
|
||||
let(:event) { Raven::Event.new(required_options.merge(payload)) }
|
||||
let(:result_hash) { described_class.call(event).to_hash }
|
||||
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -4,7 +4,15 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
|
||||
describe '.call' do
|
||||
let(:event) { Raven::Event.from_exception(exception, data) }
|
||||
let(:required_options) do
|
||||
{
|
||||
configuration: Raven.configuration,
|
||||
context: Raven.context,
|
||||
breadcrumbs: Raven.breadcrumbs
|
||||
}
|
||||
end
|
||||
|
||||
let(:event) { Raven::Event.from_exception(exception, required_options.merge(data)) }
|
||||
let(:result_hash) { described_class.call(event).to_hash }
|
||||
|
||||
context 'when there is no GRPC exception' do
|
||||
|
|
|
|||
|
|
@ -95,7 +95,15 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
|
|||
end
|
||||
|
||||
describe '.call' do
|
||||
let(:event) { Raven::Event.new(wrapped_value) }
|
||||
let(:required_options) do
|
||||
{
|
||||
configuration: Raven.configuration,
|
||||
context: Raven.context,
|
||||
breadcrumbs: Raven.breadcrumbs
|
||||
}
|
||||
end
|
||||
|
||||
let(:event) { Raven::Event.new(required_options.merge(wrapped_value)) }
|
||||
let(:result_hash) { described_class.call(event).to_hash }
|
||||
|
||||
context 'when there is Sidekiq data' do
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ RSpec.describe Emails::Profile do
|
|||
end
|
||||
|
||||
it 'mentioned the time' do
|
||||
is_expected.to have_body_text current_time.strftime('%Y-%m-%d %l:%M:%S %p %Z')
|
||||
is_expected.to have_body_text current_time.strftime('%Y-%m-%d %k:%M:%S %Z')
|
||||
end
|
||||
|
||||
it 'includes a link to the change password documentation' do
|
||||
|
|
|
|||
|
|
@ -75,6 +75,22 @@ RSpec.describe Ci::Runner do
|
|||
expect { create(:group, runners: [project_runner]) }
|
||||
.to raise_error(ActiveRecord::RecordInvalid)
|
||||
end
|
||||
|
||||
context 'when runner has config' do
|
||||
it 'is valid' do
|
||||
runner = build(:ci_runner, config: { gpus: "all" })
|
||||
|
||||
expect(runner).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'when runner has an invalid config' do
|
||||
it 'is invalid' do
|
||||
runner = build(:ci_runner, config: { test: 1 })
|
||||
|
||||
expect(runner).not_to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'cost factors validations' do
|
||||
|
|
@ -653,7 +669,7 @@ RSpec.describe Ci::Runner do
|
|||
describe '#heartbeat' do
|
||||
let(:runner) { create(:ci_runner, :project) }
|
||||
|
||||
subject { runner.heartbeat(architecture: '18-bit') }
|
||||
subject { runner.heartbeat(architecture: '18-bit', config: { gpus: "all" }) }
|
||||
|
||||
context 'when database was updated recently' do
|
||||
before do
|
||||
|
|
@ -701,6 +717,7 @@ RSpec.describe Ci::Runner do
|
|||
def does_db_update
|
||||
expect { subject }.to change { runner.reload.read_attribute(:contacted_at) }
|
||||
.and change { runner.reload.read_attribute(:architecture) }
|
||||
.and change { runner.reload.read_attribute(:config) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ RSpec.describe GroupMember do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'delegations' do
|
||||
it { is_expected.to delegate_method(:update_two_factor_requirement).to(:user).allow_nil }
|
||||
end
|
||||
|
||||
describe '.access_level_roles' do
|
||||
it 'returns Gitlab::Access.options_with_owner' do
|
||||
expect(described_class.access_level_roles).to eq(Gitlab::Access.options_with_owner)
|
||||
|
|
@ -93,6 +97,18 @@ RSpec.describe GroupMember do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
context 'for an orphaned member' do
|
||||
let!(:orphaned_group_member) do
|
||||
create(:group_member).tap { |member| member.update_column(:user_id, nil) }
|
||||
end
|
||||
|
||||
it 'does not raise an error' do
|
||||
expect { orphaned_group_member.destroy! }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#after_accept_invite' do
|
||||
it 'calls #update_two_factor_requirement' do
|
||||
email = 'foo@email.com'
|
||||
|
|
|
|||
|
|
@ -58,6 +58,16 @@ RSpec.describe ProjectMember do
|
|||
maintainer.destroy!
|
||||
expect(Event.recent.first).to be_left_action
|
||||
end
|
||||
|
||||
context 'for an orphaned member' do
|
||||
let!(:orphaned_project_member) do
|
||||
owner.tap { |member| member.update_column(:user_id, nil) }
|
||||
end
|
||||
|
||||
it 'does not raise an error' do
|
||||
expect { orphaned_project_member.destroy! }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.import_team' do
|
||||
|
|
|
|||
|
|
@ -3912,14 +3912,6 @@ RSpec.describe MergeRequest, factory_default: :keep do
|
|||
let(:service_class) { 'Ci::CompareMetricsReportsService' }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
|
||||
context 'with the metrics report flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(merge_base_pipeline_for_metrics_comparison: false)
|
||||
end
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when service class is Ci::CompareCodequalityReportsService' do
|
||||
|
|
|
|||
|
|
@ -1,105 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fileutils'
|
||||
|
||||
require 'excon'
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Unicorn' do
|
||||
before(:all) do
|
||||
project_root = File.expand_path('../..', __dir__)
|
||||
|
||||
config_lines = File.read('config/unicorn.rb.example')
|
||||
.gsub('/home/git/gitlab', project_root)
|
||||
.gsub('/home/git', project_root)
|
||||
.split("\n")
|
||||
|
||||
# Remove these because they make setup harder.
|
||||
config_lines = config_lines.reject do |line|
|
||||
%w[
|
||||
worker_processes
|
||||
listen
|
||||
pid
|
||||
stderr_path
|
||||
stdout_path
|
||||
].any? { |prefix| line.start_with?(prefix) }
|
||||
end
|
||||
|
||||
config_lines << "working_directory '#{Rails.root}'"
|
||||
|
||||
# We want to have exactly 1 worker process because that makes it
|
||||
# predictable which process will handle our requests.
|
||||
config_lines << 'worker_processes 1'
|
||||
|
||||
@socket_path = File.join(project_root, 'tmp/tests/unicorn.socket')
|
||||
config_lines << "listen '#{@socket_path}'"
|
||||
|
||||
ready_file = File.join(project_root, 'tmp/tests/unicorn-worker-ready')
|
||||
FileUtils.rm_f(ready_file)
|
||||
after_fork_index = config_lines.index { |l| l.start_with?('after_fork') }
|
||||
config_lines.insert(after_fork_index + 1, "File.write('#{ready_file}', Process.pid)")
|
||||
|
||||
config_path = File.join(project_root, 'tmp/tests/unicorn.rb')
|
||||
File.write(config_path, config_lines.join("\n") + "\n")
|
||||
|
||||
cmd = %W[unicorn -E test -c #{config_path} spec/rack_servers/configs/config.ru]
|
||||
@unicorn_master_pid = spawn(*cmd)
|
||||
wait_unicorn_boot!(@unicorn_master_pid, ready_file)
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
%w[SIGQUIT SIGTERM SIGKILL].each do |signal|
|
||||
it "has a worker that self-terminates on signal #{signal}" do
|
||||
response = Excon.get('unix://', socket: @socket_path)
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
worker_pid = response.body.to_i
|
||||
expect(worker_pid).to be > 0
|
||||
|
||||
begin
|
||||
Excon.post("unix://?#{signal}", socket: @socket_path)
|
||||
rescue Excon::Error::Socket
|
||||
# The connection may be closed abruptly
|
||||
end
|
||||
|
||||
expect(pid_gone?(worker_pid)).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
webmock_enable!
|
||||
Process.kill('TERM', @unicorn_master_pid)
|
||||
end
|
||||
|
||||
def wait_unicorn_boot!(master_pid, ready_file)
|
||||
# We have seen the boot timeout after 2 minutes in CI so let's set it to 5 minutes.
|
||||
timeout = 5 * 60
|
||||
timeout.times do
|
||||
return if File.exist?(ready_file)
|
||||
|
||||
pid = Process.waitpid(master_pid, Process::WNOHANG)
|
||||
raise "unicorn failed to boot: #{$?}" unless pid.nil?
|
||||
|
||||
sleep 1
|
||||
end
|
||||
|
||||
raise "unicorn boot timed out after #{timeout} seconds"
|
||||
end
|
||||
|
||||
def pid_gone?(pid)
|
||||
# Worker termination should take less than a second. That makes 10
|
||||
# seconds a generous timeout.
|
||||
10.times do
|
||||
begin
|
||||
Process.kill(0, pid)
|
||||
rescue Errno::ESRCH
|
||||
return true
|
||||
end
|
||||
|
||||
sleep 1
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
|
|
@ -439,6 +439,13 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
|
|||
end
|
||||
end
|
||||
|
||||
it "sets the runner's config" do
|
||||
request_job info: { 'config' => { 'gpus' => 'all', 'ignored' => 'hello' } }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(runner.reload.config).to eq( { 'gpus' => 'all' } )
|
||||
end
|
||||
|
||||
it "sets the runner's ip_address" do
|
||||
post api('/jobs/request'),
|
||||
params: { token: runner.token },
|
||||
|
|
|
|||
|
|
@ -195,6 +195,14 @@ RSpec.describe DesignManagement::CopyDesignCollection::CopyService, :clean_gitla
|
|||
expect { subject }.to change { target_repository.branch_names }.from([]).to(['master'])
|
||||
end
|
||||
|
||||
it 'does not create default branch when one exists' do
|
||||
target_repository.create_if_not_exists
|
||||
target_repository.create_file(user, '.meta', '.gitlab', branch_name: 'new-branch', message: 'message')
|
||||
|
||||
expect { subject }.not_to change { target_repository.branch_names }
|
||||
expect(target_repository.branch_names).to eq(['new-branch'])
|
||||
end
|
||||
|
||||
it 'leaves the design collection in the correct copy state' do
|
||||
subject
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,12 @@ RSpec.shared_examples 'assignee NOT username filter' do
|
|||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'assignee OR filter' do
|
||||
it 'returns issuables assigned to the given users' do
|
||||
expect(issuables).to contain_exactly(*expected_issuables)
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'no assignee filter' do
|
||||
let(:params) { { assignee_id: 'None' } }
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue