diff options
41 files changed, 394 insertions, 101 deletions
diff --git a/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue b/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue index 29d2cca6aed..99d77a75c23 100644 --- a/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue +++ b/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue @@ -126,10 +126,6 @@ export default { {{ __('No forks available to you.') }}<br /> <span v-html="noForkText"></span> </template> - <gl-link :href="helpPagePath" class="help-link" target="_blank"> - <span class="sr-only">{{ __('Read more') }}</span> - <i class="fa fa-question-circle" aria-hidden="true"></i> - </gl-link> </p> </div> </div> diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index a12029d2419..e75c1379dfb 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -287,8 +287,8 @@ list-style: none; padding: 0 1px; - a:not(.help-link), - button:not(.btn), + a, + button:not(.dropdown-toggle,.ci-action-icon-container), .menu-item { @include dropdown-link; } diff --git a/app/helpers/storage_helper.rb b/app/helpers/storage_helper.rb index ecf37bae6b3..ce810433a3a 100644 --- a/app/helpers/storage_helper.rb +++ b/app/helpers/storage_helper.rb @@ -17,6 +17,6 @@ module StorageHelper counter_lfs_objects: storage_counter(statistics.lfs_objects_size) } - _("%{counter_repositories} repositories, %{counter_wikis} wikis, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS") % counters + _("Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}") % counters end end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 02de080e0ba..db673cace81 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -182,7 +182,7 @@ class IssuableBaseService < BaseService # To be overridden by subclasses end - def before_update(issuable) + def before_update(issuable, skip_spam_check: false) # To be overridden by subclasses end @@ -257,7 +257,7 @@ class IssuableBaseService < BaseService last_edited_at: Time.now, last_edited_by: current_user)) - before_update(issuable) + before_update(issuable, skip_spam_check: true) if issuable.with_transaction_returning_status { issuable.save } # We do not touch as it will affect a update on updated_at field diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 6b9f23f24cd..7cd825aa967 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -17,8 +17,8 @@ module Issues super end - def before_update(issue) - spam_check(issue, current_user) + def before_update(issue, skip_spam_check: false) + spam_check(issue, current_user) unless skip_spam_check end def handle_changes(issue, options) diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index f524d35d79e..98230684d56 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -43,11 +43,7 @@ = render_if_exists 'admin/namespace_plan_info', namespace: @group %li - %span.light= _('Storage:') - %strong= storage_counter(@group.storage_size) - ( - = storage_counters_details(@group) - ) + = render 'shared/storage_counter_statistics', storage_size: @group.storage_size, storage_details: @group %li %span.light= _('Group Git LFS status:') diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index e23accc1ea9..0fae8060b32 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -73,11 +73,7 @@ = @project.repository.relative_path %li - %span.light= _('Storage:') - %strong= storage_counter(@project.statistics&.storage_size) - - if @project.statistics - = surround '(', ')' do - = storage_counters_details(@project.statistics) + = render 'shared/storage_counter_statistics', storage_size: @project.statistics&.storage_size, storage_details: @project.statistics %li %span.light last commit: diff --git a/app/views/shared/_storage_counter_statistics.html.haml b/app/views/shared/_storage_counter_statistics.html.haml new file mode 100644 index 00000000000..99b2323ca82 --- /dev/null +++ b/app/views/shared/_storage_counter_statistics.html.haml @@ -0,0 +1,4 @@ +%span.light= _('Storage:') +%strong= storage_counter(storage_size) +- if storage_details + (#{storage_counters_details(storage_details)}) diff --git a/changelogs/unreleased/61284-frontend-follow-up-from-add-packages_size-to-projectstatistics.yml b/changelogs/unreleased/61284-frontend-follow-up-from-add-packages_size-to-projectstatistics.yml new file mode 100644 index 00000000000..3445057f7fb --- /dev/null +++ b/changelogs/unreleased/61284-frontend-follow-up-from-add-packages_size-to-projectstatistics.yml @@ -0,0 +1,5 @@ +--- +title: Improved readability of storage statistics in group / project admin area +merge_request: 30406 +author: +type: other diff --git a/changelogs/unreleased/issue_64021.yml b/changelogs/unreleased/issue_64021.yml new file mode 100644 index 00000000000..088d9348619 --- /dev/null +++ b/changelogs/unreleased/issue_64021.yml @@ -0,0 +1,5 @@ +--- +title: Skip spam check for task list updates +merge_request: 30279 +author: +type: fixed diff --git a/config/application.rb b/config/application.rb index c5ef6a2c60d..edf8b3e87f9 100644 --- a/config/application.rb +++ b/config/application.rb @@ -6,6 +6,7 @@ Bundler.require(:default, Rails.env) module Gitlab class Application < Rails::Application + require_dependency Rails.root.join('lib/gitlab') require_dependency Rails.root.join('lib/gitlab/redis/wrapper') require_dependency Rails.root.join('lib/gitlab/redis/cache') require_dependency Rails.root.join('lib/gitlab/redis/queues') diff --git a/config/initializers/0_license.rb b/config/initializers/0_license.rb new file mode 100644 index 00000000000..f750022dfdf --- /dev/null +++ b/config/initializers/0_license.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +Gitlab.ee do + begin + public_key_file = File.read(Rails.root.join(".license_encryption_key.pub")) + public_key = OpenSSL::PKey::RSA.new(public_key_file) + Gitlab::License.encryption_key = public_key + rescue + warn "WARNING: No valid license encryption key provided." + end + + # Needed to run migration + if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.data_source_exists?('licenses') + message = LicenseHelper.license_message(signed_in: true, is_admin: true, in_html: false) + if ::License.block_changes? && message.present? + warn "WARNING: #{message}" + end + end +end diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index bf187e9a282..0b8a6607250 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -76,6 +76,7 @@ Gitlab.ee do Settings['smartcard'] ||= Settingslogic.new({}) Settings.smartcard['enabled'] = false if Settings.smartcard['enabled'].nil? Settings.smartcard['client_certificate_required_port'] = 3444 if Settings.smartcard['client_certificate_required_port'].nil? + Settings.smartcard['required_for_git_access'] = false if Settings.smartcard['required_for_git_access'].nil? end Settings['omniauth'] ||= Settingslogic.new({}) diff --git a/config/initializers/console_message.rb b/config/initializers/console_message.rb index 05eb395028d..04c109aa844 100644 --- a/config/initializers/console_message.rb +++ b/config/initializers/console_message.rb @@ -2,9 +2,18 @@ if defined?(Rails::Console) # note that this will not print out when using `spring` justify = 15 - puts "-------------------------------------------------------------------------------------" + + puts '-' * 80 puts " GitLab:".ljust(justify) + "#{Gitlab::VERSION} (#{Gitlab.revision})" puts " GitLab Shell:".ljust(justify) + "#{Gitlab::VersionInfo.parse(Gitlab::Shell.new.version)}" puts " #{Gitlab::Database.human_adapter_name}:".ljust(justify) + Gitlab::Database.version - puts "-------------------------------------------------------------------------------------" + + Gitlab.ee do + if Gitlab::Geo.enabled? + puts " Geo enabled:".ljust(justify) + 'yes' + puts " Geo server:".ljust(justify) + EE::GeoHelper.current_node_human_status + end + end + + puts '-' * 80 end diff --git a/config/initializers/elastic_client_setup.rb b/config/initializers/elastic_client_setup.rb new file mode 100644 index 00000000000..2ecb7956007 --- /dev/null +++ b/config/initializers/elastic_client_setup.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# Be sure to restart your server when you modify this file. + +require 'gitlab/current_settings' + +Gitlab.ee do + Elasticsearch::Model::Response::Records.prepend GemExtensions::Elasticsearch::Model::Response::Records + Elasticsearch::Model::Adapter::Multiple::Records.prepend GemExtensions::Elasticsearch::Model::Adapter::Multiple::Records + Elasticsearch::Model::Indexing::InstanceMethods.prepend GemExtensions::Elasticsearch::Model::Indexing::InstanceMethods + + module Elasticsearch + module Model + module Client + # This mutex is only used to synchronize *creation* of a new client, so + # all including classes can share the same client instance + CLIENT_MUTEX = Mutex.new + + cattr_accessor :cached_client + cattr_accessor :cached_config + + module ClassMethods + # Override the default ::Elasticsearch::Model::Client implementation to + # return a client configured from application settings. All including + # classes will use the same instance, which is refreshed automatically + # if the settings change. + # + # _client is present to match the arity of the overridden method, where + # it is also not used. + # + # @return [Elasticsearch::Transport::Client] + def client(_client = nil) + store = ::Elasticsearch::Model::Client + + store::CLIENT_MUTEX.synchronize do + config = Gitlab::CurrentSettings.elasticsearch_config + + if store.cached_client.nil? || config != store.cached_config + store.cached_client = ::Gitlab::Elastic::Client.build(config) + store.cached_config = config + end + end + + store.cached_client + end + end + end + end + end +end diff --git a/config/initializers/geo.rb b/config/initializers/geo.rb new file mode 100644 index 00000000000..4cc9fbf49b2 --- /dev/null +++ b/config/initializers/geo.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +Gitlab.ee do + if File.exist?(Rails.root.join('config/database_geo.yml')) + Rails.application.configure do + config.geo_database = config_for(:database_geo) + end + end + + begin + if Gitlab::Geo.connected? && Gitlab::Geo.primary? + Gitlab::Geo.current_node&.update_clone_url! + end + rescue => e + warn "WARNING: Unable to check/update clone_url_prefix for Geo: #{e}" + end +end diff --git a/config/initializers/health_check.rb b/config/initializers/health_check.rb index 959daa93f78..9f466dc39de 100644 --- a/config/initializers/health_check.rb +++ b/config/initializers/health_check.rb @@ -1,4 +1,10 @@ HealthCheck.setup do |config| config.standard_checks = %w(database migrations cache) config.full_checks = %w(database migrations cache) + + Gitlab.ee do + config.add_custom_check('geo') do + Gitlab::Geo::HealthCheck.new.perform_checks + end + end end diff --git a/config/initializers/load_balancing.rb b/config/initializers/load_balancing.rb new file mode 100644 index 00000000000..029c0ff4277 --- /dev/null +++ b/config/initializers/load_balancing.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# We need to run this initializer after migrations are done so it doesn't fail on CI + +Gitlab.ee do + if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.data_source_exists?('licenses') + if Gitlab::Database::LoadBalancing.enable? + Gitlab::Database.disable_prepared_statements + + Gitlab::Application.configure do |config| + config.middleware.use(Gitlab::Database::LoadBalancing::RackMiddleware) + end + + Gitlab::Database::LoadBalancing.configure_proxy + + # This needs to be executed after fork of clustered processes + Gitlab::Cluster::LifecycleEvents.on_worker_start do + # Service discovery must be started after configuring the proxy, as service + # discovery depends on this. + Gitlab::Database::LoadBalancing.start_service_discovery + end + + end + end +end diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 7b69cf11288..f9ef5d66bfa 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -85,6 +85,19 @@ Sidekiq.configure_server do |config| ActiveRecord::Base.establish_connection(db_config) Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}") + Gitlab.ee do + Gitlab::Mirror.configure_cron_job! + + Gitlab::Geo.configure_cron_jobs! + + if Gitlab::Geo.geo_database_configured? + Rails.configuration.geo_database['pool'] = Sidekiq.options[:concurrency] + Geo::TrackingBase.establish_connection(Rails.configuration.geo_database) + + Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{Geo::TrackingBase.connection_pool.size} (Geo tracking database)") + end + end + # Avoid autoload issue such as 'Mail::Parsers::AddressStruct' # https://github.com/mikel/mail/issues/912#issuecomment-214850355 Mail.eager_autoload! diff --git a/config/initializers/sidekiq_cluster.rb b/config/initializers/sidekiq_cluster.rb new file mode 100644 index 00000000000..baa7495aa29 --- /dev/null +++ b/config/initializers/sidekiq_cluster.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +if ENV['ENABLE_SIDEKIQ_CLUSTER'] && Gitlab.ee? + Thread.new do + Thread.current.abort_on_exception = true + + parent = Process.ppid + + loop do + sleep(5) + + # In cluster mode it's possible that the master process is SIGKILL'd. In + # this case the parent PID changes and we need to terminate ourselves. + if Process.ppid != parent + Process.kill(:TERM, Process.pid) + break + end + end + end +end diff --git a/doc/user/project/packages/npm_registry.md b/doc/user/project/packages/npm_registry.md index b2cfe10836f..481b1ce0337 100644 --- a/doc/user/project/packages/npm_registry.md +++ b/doc/user/project/packages/npm_registry.md @@ -11,11 +11,6 @@ project can have its own space to store NPM packages. NOTE: **Note:** Only [scoped](https://docs.npmjs.com/misc/scope) packages are supported. - -NOTE: **Note:** -As `@group/subgroup/project` is not a valid NPM package name, publishing a package -within a subgroup is not supported yet. - ## Enabling the NPM Registry NOTE: **Note:** @@ -36,12 +31,15 @@ get familiar with the package naming convention. ## Package naming convention -**Only packages that have the same path as the project** are supported. For - example: +**Packages must be scoped in the root namespace of the project**. The package +name may be anything but it is preferred that the project name be used unless +it is not possible due to a naming collision. For example: | Project | Package | Supported | | ---------------------- | ----------------------- | --------- | | `foo/bar` | `@foo/bar` | Yes | +| `foo/bar/baz` | `@foo/baz` | Yes | +| `foo/bar/buz` | `@foo/anything` | Yes | | `gitlab-org/gitlab-ce` | `@gitlab-org/gitlab-ce` | Yes | | `gitlab-org/gitlab-ce` | `@foo/bar` | No | @@ -113,6 +111,9 @@ npm publish You can then navigate to your project's **Packages** page and see the uploaded packages or even delete them. +If you attempt to publish a package with a name that already exists within +a given scope, you will receive a `403 Forbidden!` error. + ## Uploading a package with the same version twice If you upload a package with a same name and version twice, GitLab will show diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index 819515d7a4c..98bcc7cc09f 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -74,6 +74,13 @@ The following items will NOT be exported: - CI variables - Webhooks - Any encrypted tokens +- Merge Request Approvers +- Push Rules +- Awards + +NOTE: **Note:** +For more details on the specific data persisted in a project export, see the +[`import_export.yml`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/import_export/import_export.yml) file. ## Exporting a project and its data diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f1cfb0ac06d..9b6e8d8c8a4 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -124,9 +124,6 @@ msgstr "" msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" -msgid "%{counter_repositories} repositories, %{counter_wikis} wikis, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS" -msgstr "" - msgid "%{count} more" msgstr "" @@ -8877,6 +8874,9 @@ msgstr "" msgid "Repository storage" msgstr "" +msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}" +msgstr "" + msgid "Request Access" msgstr "" diff --git a/qa/.rspec_parallel b/qa/.rspec_parallel new file mode 100644 index 00000000000..e5927927eaa --- /dev/null +++ b/qa/.rspec_parallel @@ -0,0 +1,5 @@ +--color +--format documentation +--format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log +--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log +--require spec_helper diff --git a/qa/Gemfile b/qa/Gemfile index 12994b85322..c46be8a0362 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -1,5 +1,6 @@ source 'https://rubygems.org' +gem 'gitlab-qa' gem 'pry-byebug', '~> 3.5.1', platform: :mri gem 'capybara', '~> 2.16.1' gem 'capybara-screenshot', '~> 1.0.18' @@ -11,3 +12,4 @@ gem 'nokogiri', '~> 1.10.3' gem 'rspec-retry', '~> 0.6.1' gem 'faker', '~> 1.6', '>= 1.6.6' gem 'knapsack', '~> 1.17' +gem 'parallel_tests', '~> 2.29' diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 6b0635ed0e2..73aabf2c6ad 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -35,6 +35,7 @@ GEM faker (1.9.3) i18n (>= 0.7) ffi (1.9.25) + gitlab-qa (4.0.0) http-cookie (1.0.3) domain_name (~> 0.5) i18n (0.9.1) @@ -53,6 +54,9 @@ GEM netrc (0.11.0) nokogiri (1.10.3) mini_portile2 (~> 2.4.0) + parallel (1.17.0) + parallel_tests (2.29.0) + parallel pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) @@ -104,8 +108,10 @@ DEPENDENCIES capybara (~> 2.16.1) capybara-screenshot (~> 1.0.18) faker (~> 1.6, >= 1.6.6) + gitlab-qa knapsack (~> 1.17) nokogiri (~> 1.10.3) + parallel_tests (~> 2.29) pry-byebug (~> 3.5.1) rake (~> 12.3.0) rspec (~> 3.7) @@ -360,6 +360,7 @@ module QA module Specs autoload :Config, 'qa/specs/config' autoload :Runner, 'qa/specs/runner' + autoload :ParallelRunner, 'qa/specs/parallel_runner' module Helpers autoload :Quarantine, 'qa/specs/helpers/quarantine' diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index ed0779b93cc..2987bb1a213 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -13,6 +13,8 @@ module QA NotRespondingError = Class.new(RuntimeError) + CAPYBARA_MAX_WAIT_TIME = 10 + def initialize self.class.configure! end @@ -43,6 +45,8 @@ module QA end end + Capybara.server_port = 9887 + ENV['TEST_ENV_NUMBER'].to_i + return if Capybara.drivers.include?(:chrome) Capybara.register_driver QA::Runtime::Env.browser do |app| @@ -119,7 +123,7 @@ module QA Capybara.configure do |config| config.default_driver = QA::Runtime::Env.browser config.javascript_driver = QA::Runtime::Env.browser - config.default_max_wait_time = 10 + config.default_max_wait_time = CAPYBARA_MAX_WAIT_TIME # https://github.com/mattheworiordan/capybara-screenshot/issues/164 config.save_path = ::File.expand_path('../../tmp', __dir__) end diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 96f337dc081..d50f618ff82 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'gitlab/qa' + module QA module Runtime module Env @@ -7,6 +9,8 @@ module QA attr_writer :personal_access_token, :ldap_username, :ldap_password + ENV_VARIABLES = Gitlab::QA::Runtime::Env::ENV_VARIABLES + # The environment variables used to indicate if the environment under test # supports the given feature SUPPORTED_FEATURES = { @@ -201,6 +205,10 @@ module QA enabled?(ENV[SUPPORTED_FEATURES[feature]], default: true) end + def runtime_scenario_attributes + ENV['QA_RUNTIME_SCENARIO_ATTRIBUTES'] + end + private def remote_grid_credentials diff --git a/qa/qa/runtime/scenario.rb b/qa/qa/runtime/scenario.rb index 5067322804b..3662ebe671b 100644 --- a/qa/qa/runtime/scenario.rb +++ b/qa/qa/runtime/scenario.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'json' + module QA module Runtime ## @@ -24,6 +26,10 @@ module QA end end + def from_env(var) + JSON.parse(Runtime::Env.runtime_scenario_attributes).each { |k, v| define(k, v) } + end + def method_missing(name, *) raise ArgumentError, "Scenario attribute `#{name}` not defined!" end diff --git a/qa/qa/scenario/shared_attributes.rb b/qa/qa/scenario/shared_attributes.rb index 40d5c6b1ff1..52f50ec8c27 100644 --- a/qa/qa/scenario/shared_attributes.rb +++ b/qa/qa/scenario/shared_attributes.rb @@ -7,6 +7,7 @@ module QA attribute :gitlab_address, '--address URL', 'Address of the instance to test' attribute :enable_feature, '--enable-feature FEATURE_FLAG', 'Enable a feature before running tests' + attribute :parallel, '--parallel', 'Execute tests in parallel' end end end diff --git a/qa/qa/specs/parallel_runner.rb b/qa/qa/specs/parallel_runner.rb new file mode 100644 index 00000000000..b92fdb610b6 --- /dev/null +++ b/qa/qa/specs/parallel_runner.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'open3' + +module QA + module Specs + module ParallelRunner + module_function + + def run(args) + unless args.include?('--') + index = args.index { |opt| opt.include?('features') } + + args.insert(index, '--') if index + end + + env = {} + Runtime::Env::ENV_VARIABLES.each_key do |key| + env[key] = ENV[key] if ENV[key] + end + env['QA_RUNTIME_SCENARIO_ATTRIBUTES'] = Runtime::Scenario.attributes.to_json + env['GITLAB_QA_ACCESS_TOKEN'] = Runtime::API::Client.new(:gitlab).personal_access_token unless env['GITLAB_QA_ACCESS_TOKEN'] + + cmd = "bundle exec parallel_test -t rspec --combine-stderr --serialize-stdout -- #{args.flatten.join(' ')}" + ::Open3.popen2e(env, cmd) do |_, out, wait| + out.each { |line| puts line } + + exit wait.value.exitstatus + end + end + end + end +end diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb index f1cb9378de8..6aa08cf77b4 100644 --- a/qa/qa/specs/runner.rb +++ b/qa/qa/specs/runner.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true +require 'knapsack' require 'rspec/core' require 'rspec/expectations' -require 'knapsack' module QA module Specs @@ -17,44 +17,56 @@ module QA @options = [] end - def perform - args = [] - args.push('--tty') if tty + def paths_from_knapsack + allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator + + QA::Runtime::Logger.info '' + QA::Runtime::Logger.info 'Report specs:' + QA::Runtime::Logger.info allocator.report_node_tests.join(', ') + QA::Runtime::Logger.info '' + QA::Runtime::Logger.info 'Leftover specs:' + QA::Runtime::Logger.info allocator.leftover_node_tests.join(', ') + QA::Runtime::Logger.info '' + + ['--', allocator.node_tests] + end + + def rspec_tags + tags_for_rspec = [] if tags.any? - tags.each { |tag| args.push(['--tag', tag.to_s]) } + tags.each { |tag| tags_for_rspec.push(['--tag', tag.to_s]) } else - args.push(%w[--tag ~orchestrated]) unless (%w[-t --tag] & options).any? + tags_for_rspec.push(%w[--tag ~orchestrated]) unless (%w[-t --tag] & options).any? end - args.push(%w[--tag ~skip_signup_disabled]) if QA::Runtime::Env.signup_disabled? + tags_for_rspec.push(%w[--tag ~skip_signup_disabled]) if QA::Runtime::Env.signup_disabled? QA::Runtime::Env.supported_features.each_key do |key| - args.push(["--tag", "~requires_#{key}"]) unless QA::Runtime::Env.can_test? key + tags_for_rspec.push(%W[--tag ~requires_#{key}]) unless QA::Runtime::Env.can_test? key end - args.push(options) + tags_for_rspec + end - Runtime::Browser.configure! + def perform + args = [] + args.push('--tty') if tty + args.push(rspec_tags) + args.push(options) if Runtime::Env.knapsack? - allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator - - QA::Runtime::Logger.info '' - QA::Runtime::Logger.info 'Report specs:' - QA::Runtime::Logger.info allocator.report_node_tests.join(', ') - QA::Runtime::Logger.info '' - QA::Runtime::Logger.info 'Leftover specs:' - QA::Runtime::Logger.info allocator.leftover_node_tests.join(', ') - QA::Runtime::Logger.info '' - - args.push(['--', allocator.node_tests]) + args.push(paths_from_knapsack) else args.push(DEFAULT_TEST_PATH_ARGS) unless options.any? { |opt| opt =~ %r{/features/} } end - RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status| - abort if status.nonzero? + if Runtime::Scenario.attributes[:parallel] + ParallelRunner.run(args.flatten) + else + RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status| + abort if status.nonzero? + end end end end diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb index 0f1ed039149..92a4f7b40e6 100644 --- a/qa/spec/page/logging_spec.rb +++ b/qa/spec/page/logging_spec.rb @@ -91,26 +91,26 @@ describe QA::Support::Page::Logging do it 'logs has_element?' do expect { subject.has_element?(:element) } - .to output(/has_element\? :element \(wait: 2\) returned: true/).to_stdout_from_any_process + .to output(/has_element\? :element \(wait: #{QA::Runtime::Browser::CAPYBARA_MAX_WAIT_TIME}\) returned: true/).to_stdout_from_any_process end it 'logs has_element? with text' do expect { subject.has_element?(:element, text: "some text") } - .to output(/has_element\? :element with text \"some text\" \(wait: 2\) returned: true/).to_stdout_from_any_process + .to output(/has_element\? :element with text \"some text\" \(wait: #{QA::Runtime::Browser::CAPYBARA_MAX_WAIT_TIME}\) returned: true/).to_stdout_from_any_process end it 'logs has_no_element?' do allow(page).to receive(:has_no_css?).and_return(true) expect { subject.has_no_element?(:element) } - .to output(/has_no_element\? :element \(wait: 2\) returned: true/).to_stdout_from_any_process + .to output(/has_no_element\? :element \(wait: #{QA::Runtime::Browser::CAPYBARA_MAX_WAIT_TIME}\) returned: true/).to_stdout_from_any_process end it 'logs has_no_element? with text' do allow(page).to receive(:has_no_css?).and_return(true) expect { subject.has_no_element?(:element, text: "more text") } - .to output(/has_no_element\? :element with text \"more text\" \(wait: 2\) returned: true/).to_stdout_from_any_process + .to output(/has_no_element\? :element with text \"more text\" \(wait: #{QA::Runtime::Browser::CAPYBARA_MAX_WAIT_TIME}\) returned: true/).to_stdout_from_any_process end it 'logs has_text?' do diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb index f25dbf3a8ab..21bfd2876a9 100644 --- a/qa/spec/spec_helper.rb +++ b/qa/spec/spec_helper.rb @@ -8,6 +8,10 @@ if ENV['CI'] && QA::Runtime::Env.knapsack? && !ENV['NO_KNAPSACK'] Knapsack::Adapters::RSpecAdapter.bind end +QA::Runtime::Browser.configure! + +QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes) if QA::Runtime::Env.runtime_scenario_attributes + %w[helpers shared_examples].each do |d| Dir[::File.join(__dir__, d, '**', '*.rb')].each { |f| require f } end diff --git a/qa/spec/specs/parallel_runner_spec.rb b/qa/spec/specs/parallel_runner_spec.rb new file mode 100644 index 00000000000..67d94a1f648 --- /dev/null +++ b/qa/spec/specs/parallel_runner_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +describe QA::Specs::ParallelRunner do + include Helpers::StubENV + + before do + allow(QA::Runtime::Scenario).to receive(:attributes).and_return(parallel: true) + stub_env('GITLAB_QA_ACCESS_TOKEN', 'skip_token_creation') + end + + it 'passes args to parallel_tests' do + expect_cli_arguments(['--tag', '~orchestrated', *QA::Specs::Runner::DEFAULT_TEST_PATH_ARGS]) + + subject.run(['--tag', '~orchestrated', *QA::Specs::Runner::DEFAULT_TEST_PATH_ARGS]) + end + + it 'passes a given test path to parallel_tests and adds a separator' do + expect_cli_arguments(%w[-- qa/specs/features/foo]) + + subject.run(%w[qa/specs/features/foo]) + end + + it 'passes tags and test paths to parallel_tests and adds a separator' do + expect_cli_arguments(%w[--tag smoke -- qa/specs/features/foo qa/specs/features/bar]) + + subject.run(%w[--tag smoke qa/specs/features/foo qa/specs/features/bar]) + end + + it 'passes tags and test paths with separators to parallel_tests' do + expect_cli_arguments(%w[-- --tag smoke -- qa/specs/features/foo qa/specs/features/bar]) + + subject.run(%w[-- --tag smoke -- qa/specs/features/foo qa/specs/features/bar]) + end + + it 'passes supported environment variables' do + # Test only env vars starting with GITLAB because some of the others + # affect how the runner behaves, and we're not concerned with those + # behaviors in this test + gitlab_env_vars = QA::Runtime::Env::ENV_VARIABLES.reject { |v| !v.start_with?('GITLAB') } + + gitlab_env_vars.each do |k, v| + stub_env(k, v) + end + + gitlab_env_vars['QA_RUNTIME_SCENARIO_ATTRIBUTES'] = '{"parallel":true}' + + expect_cli_arguments([], gitlab_env_vars) + + subject.run([]) + end + + def expect_cli_arguments(arguments, env = { 'QA_RUNTIME_SCENARIO_ATTRIBUTES' => '{"parallel":true}' }) + cmd = "bundle exec parallel_test -t rspec --combine-stderr --serialize-stdout -- #{arguments.join(' ')}" + expect(Open3).to receive(:popen2e) + .with(hash_including(env), cmd) + .and_return(0) + end +end diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb index f94145d148e..6c533c6dc7d 100644 --- a/qa/spec/specs/runner_spec.rb +++ b/qa/spec/specs/runner_spec.rb @@ -58,11 +58,11 @@ describe QA::Specs::Runner do end end - context 'when "-- qa/specs/features/foo" is set as options' do - subject { described_class.new.tap { |runner| runner.options = %w[-- qa/specs/features/foo] } } + context 'when "--tag smoke" and "qa/specs/features/foo" are set as options' do + subject { described_class.new.tap { |runner| runner.options = %w[--tag smoke qa/specs/features/foo] } } - it 'passes the given tests path and excludes the orchestrated tag' do - expect_rspec_runner_arguments(['--tag', '~orchestrated', '--', 'qa/specs/features/foo']) + it 'focuses on the given tag and includes the path without excluding the orchestrated tag' do + expect_rspec_runner_arguments(['--tag', 'smoke', 'qa/specs/features/foo']) subject.perform end diff --git a/spec/features/admin/admin_sees_project_statistics_spec.rb b/spec/features/admin/admin_sees_project_statistics_spec.rb index b5323a1c76d..ecd0aab925b 100644 --- a/spec/features/admin/admin_sees_project_statistics_spec.rb +++ b/spec/features/admin/admin_sees_project_statistics_spec.rb @@ -15,7 +15,7 @@ describe "Admin > Admin sees project statistics" do let(:project) { create(:project, :repository) } it "shows project statistics" do - expect(page).to have_content("Storage: 0 Bytes (0 Bytes repositories, 0 Bytes wikis, 0 Bytes build artifacts, 0 Bytes LFS)") + expect(page).to have_content("Storage: 0 Bytes (Repository: 0 Bytes / Wikis: 0 Bytes / Build Artifacts: 0 Bytes / LFS: 0 Bytes)") end end diff --git a/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap b/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap index ba0ee8dfd59..fd307ce5ab3 100644 --- a/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap +++ b/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap @@ -28,23 +28,6 @@ exports[`Confidential merge request project form group component renders empty s </a> and set the forks visiblity to private. </span> - - <gllink-stub - class="help-link" - href="/help" - target="_blank" - > - <span - class="sr-only" - > - Read more - </span> - - <i - aria-hidden="true" - class="fa fa-question-circle" - /> - </gllink-stub> </p> </div> </div> @@ -78,23 +61,6 @@ exports[`Confidential merge request project form group component renders fork dr </a> and set the forks visiblity to private. </span> - - <gllink-stub - class="help-link" - href="/help" - target="_blank" - > - <span - class="sr-only" - > - Read more - </span> - - <i - aria-hidden="true" - class="fa fa-question-circle" - /> - </gllink-stub> </p> </div> </div> diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb index 62c00964524..4bd0fbb76ca 100644 --- a/spec/helpers/storage_helper_spec.rb +++ b/spec/helpers/storage_helper_spec.rb @@ -31,7 +31,7 @@ describe StorageHelper do build_artifacts_size: 30.megabytes)) end - let(:message) { '10 KB repositories, 10 Bytes wikis, 30 MB build artifacts, 20 GB LFS' } + let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / LFS: 20 GB' } it 'works on ProjectStatistics' do expect(helper.storage_counters_details(project.statistics)).to eq(message) diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 28fa5d12d9c..468e7c286d5 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -480,6 +480,22 @@ describe Issues::UpdateService, :mailer do update_issue(description: "- [x] Task 1\n- [X] Task 2") end + it 'does not check for spam on task status change' do + params = { + update_task: { + index: 1, + checked: false, + line_source: '- [x] Task 1', + line_number: 1 + } + } + service = described_class.new(project, user, params) + + expect(service).not_to receive(:spam_check) + + service.execute(issue) + end + it 'creates system note about task status change' do note1 = find_note('marked the task **Task 1** as completed') note2 = find_note('marked the task **Task 2** as completed') |