1# frozen_string_literal: true
2
3#  $" is $LOADED_FEATURES, but RuboCop didn't like it
4if $".include?(File.expand_path('fast_spec_helper.rb', __dir__))
5  warn 'Detected fast_spec_helper is loaded first than spec_helper.'
6  warn 'If running test files using both spec_helper and fast_spec_helper,'
7  warn 'make sure test file with spec_helper is loaded first.'
8  abort 'Aborting...'
9end
10
11# Enable deprecation warnings by default and make them more visible
12# to developers to ease upgrading to newer Ruby versions.
13Warning[:deprecated] = true unless ENV.key?('SILENCE_DEPRECATIONS')
14
15require './spec/deprecation_toolkit_env'
16DeprecationToolkitEnv.configure!
17
18require './spec/knapsack_env'
19KnapsackEnv.configure!
20
21require './spec/simplecov_env'
22SimpleCovEnv.start!
23
24require './spec/crystalball_env'
25CrystalballEnv.start!
26
27ENV["RAILS_ENV"] = 'test'
28ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true'
29ENV["RSPEC_ALLOW_INVALID_URLS"] = 'true'
30
31require_relative '../config/environment'
32
33require 'rspec/mocks'
34require 'rspec/rails'
35require 'rspec/retry'
36require 'rspec-parameterized'
37require 'shoulda/matchers'
38require 'test_prof/recipes/rspec/let_it_be'
39require 'test_prof/factory_default'
40require 'parslet/rig/rspec'
41
42rspec_profiling_is_configured =
43  ENV['RSPEC_PROFILING_POSTGRES_URL'].present? ||
44  ENV['RSPEC_PROFILING']
45branch_can_be_profiled =
46  (ENV['CI_COMMIT_REF_NAME'] == 'master' ||
47    ENV['CI_COMMIT_REF_NAME'] =~ /rspec-profile/)
48
49if rspec_profiling_is_configured && (!ENV.key?('CI') || branch_can_be_profiled)
50  require 'rspec_profiling/rspec'
51end
52
53# require rainbow gem String monkeypatch, so we can test SystemChecks
54require 'rainbow/ext/string'
55Rainbow.enabled = false
56
57require_relative('../ee/spec/spec_helper') if Gitlab.ee?
58require_relative('../jh/spec/spec_helper') if Gitlab.jh?
59
60# Requires supporting ruby files with custom matchers and macros, etc,
61# in spec/support/ and its subdirectories.
62# Requires helpers, and shared contexts/examples first since they're used in other support files
63
64# Load these first since they may be required by other helpers
65require Rails.root.join("spec/support/helpers/git_helpers.rb")
66require Rails.root.join("spec/support/helpers/stub_requests.rb")
67
68# Then the rest
69Dir[Rails.root.join("spec/support/helpers/*.rb")].sort.each { |f| require f }
70Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].sort.each { |f| require f }
71Dir[Rails.root.join("spec/support/shared_examples/*.rb")].sort.each { |f| require f }
72Dir[Rails.root.join("spec/support/**/*.rb")].sort.each { |f| require f }
73
74require_relative '../tooling/quality/test_level'
75
76quality_level = Quality::TestLevel.new
77
78RSpec.configure do |config|
79  config.use_transactional_fixtures = true
80  config.use_instantiated_fixtures = false
81  config.fixture_path = Rails.root
82
83  config.verbose_retry = true
84  config.display_try_failure_messages = true
85
86  config.infer_spec_type_from_file_location!
87
88  # Add :full_backtrace tag to an example if full_backtrace output is desired
89  config.before(:each, full_backtrace: true) do |example|
90    config.full_backtrace = true
91  end
92
93  # Attempt to troubleshoot https://gitlab.com/gitlab-org/gitlab/-/issues/297359
94  if ENV['CI']
95    config.after do |example|
96      if example.exception.is_a?(GRPC::Unavailable)
97        warn "=== gRPC unavailable detected, process list:"
98        processes = `ps -ef | grep toml`
99        warn processes
100        warn "=== free memory"
101        warn `free -m`
102        warn "=== uptime"
103        warn `uptime`
104        warn "=== Prometheus metrics:"
105        warn `curl -s -o log/gitaly-metrics.log http://localhost:9236/metrics`
106        warn "=== Taking goroutine dump in log/goroutines.log..."
107        warn `curl -s -o log/goroutines.log http://localhost:9236/debug/pprof/goroutine?debug=2`
108      end
109    end
110  else
111    # Allow running `:focus` examples locally,
112    # falling back to all tests when there is no `:focus` example.
113    config.filter_run focus: true
114    config.run_all_when_everything_filtered = true
115
116    # Re-run failures locally with `--only-failures`
117    config.example_status_persistence_file_path = './spec/examples.txt'
118  end
119
120  config.define_derived_metadata(file_path: %r{(ee)?/spec/.+_spec\.rb\z}) do |metadata|
121    location = metadata[:location]
122
123    metadata[:level] = quality_level.level_for(location)
124    metadata[:api] = true if location =~ %r{/spec/requests/api/}
125
126    # Do not overwrite migration if it's already set
127    unless metadata.key?(:migration)
128      metadata[:migration] = true if metadata[:level] == :migration
129    end
130
131    # Do not overwrite schema if it's already set
132    unless metadata.key?(:schema)
133      metadata[:schema] = :latest if quality_level.background_migration?(location)
134    end
135
136    # Do not overwrite type if it's already set
137    unless metadata.key?(:type)
138      match = location.match(%r{/spec/([^/]+)/})
139      metadata[:type] = match[1].singularize.to_sym if match
140    end
141
142    # Admin controller specs get auto admin mode enabled since they are
143    # protected by the 'EnforcesAdminAuthentication' concern
144    metadata[:enable_admin_mode] = true if location =~ %r{(ee)?/spec/controllers/admin/}
145  end
146
147  config.define_derived_metadata(file_path: %r{(ee)?/spec/.+_docs\.rb\z}) do |metadata|
148    metadata[:type] = :feature
149  end
150
151  config.include LicenseHelpers
152  config.include ActiveJob::TestHelper
153  config.include ActiveSupport::Testing::TimeHelpers
154  config.include CycleAnalyticsHelpers
155  config.include FactoryBot::Syntax::Methods
156  config.include FixtureHelpers
157  config.include NonExistingRecordsHelpers
158  config.include GitlabRoutingHelper
159  config.include StubExperiments
160  config.include StubGitlabCalls
161  config.include NextFoundInstanceOf
162  config.include NextInstanceOf
163  config.include TestEnv
164  config.include FileReadHelpers
165  config.include Database::MultipleDatabases
166  config.include Devise::Test::ControllerHelpers, type: :controller
167  config.include Devise::Test::ControllerHelpers, type: :view
168  config.include Devise::Test::IntegrationHelpers, type: :feature
169  config.include Devise::Test::IntegrationHelpers, type: :request
170  config.include LoginHelpers, type: :feature
171  config.include SearchHelpers, type: :feature
172  config.include WaitHelpers, type: :feature
173  config.include WaitForRequests, type: :feature
174  config.include EmailHelpers, :mailer, type: :mailer
175  config.include Warden::Test::Helpers, type: :request
176  config.include Gitlab::Routing, type: :routing
177  config.include ApiHelpers, :api
178  config.include CookieHelper, :js
179  config.include InputHelper, :js
180  config.include SelectionHelper, :js
181  config.include InspectRequests, :js
182  config.include LiveDebugger, :js
183  config.include MigrationsHelpers, :migration
184  config.include RedisHelpers
185  config.include Rails.application.routes.url_helpers, type: :routing
186  config.include PolicyHelpers, type: :policy
187  config.include MemoryUsageHelper
188  config.include ExpectRequestWithStatus, type: :request
189  config.include IdempotentWorkerHelper, type: :worker
190  config.include RailsHelpers
191  config.include SidekiqMiddleware
192  config.include StubActionCableConnection, type: :channel
193  config.include StubSpamServices
194
195  include StubFeatureFlags
196
197  if ENV['CI'] || ENV['RETRIES']
198    # This includes the first try, i.e. tests will be run 4 times before failing.
199    config.default_retry_count = ENV.fetch('RETRIES', 3).to_i + 1
200
201    # Do not retry controller tests because rspec-retry cannot properly
202    # reset the controller which may contain data from last attempt. See
203    # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73360
204    config.prepend_before(:each, type: :controller) do |example|
205      example.metadata[:retry] = 1
206    end
207
208    config.exceptions_to_hard_fail = [DeprecationToolkitEnv::DeprecationBehaviors::SelectiveRaise::RaiseDisallowedDeprecation]
209  end
210
211  if ENV['FLAKY_RSPEC_GENERATE_REPORT']
212    require_relative '../tooling/rspec_flaky/listener'
213
214    config.reporter.register_listener(
215      RspecFlaky::Listener.new,
216      :example_passed,
217      :dump_summary)
218  end
219
220  config.before(:suite) do
221    Timecop.safe_mode = true
222    TestEnv.init
223
224    # Reload all feature flags definitions
225    Feature.register_definitions
226
227    # Enable all features by default for testing
228    # Reset any changes in after hook.
229    stub_all_feature_flags
230
231    TestEnv.seed_db
232  end
233
234  config.after(:all) do
235    TestEnv.clean_test_path
236  end
237
238  # We can't use an `around` hook here because the wrapping transaction
239  # is not yet opened at the time that is triggered
240  config.prepend_before do
241    ApplicationRecord.set_open_transactions_baseline
242    ::Ci::ApplicationRecord.set_open_transactions_baseline
243  end
244
245  config.append_before do
246    Thread.current[:current_example_group] = ::RSpec.current_example.metadata[:example_group]
247  end
248
249  config.append_after do
250    ApplicationRecord.reset_open_transactions_baseline
251    ::Ci::ApplicationRecord.reset_open_transactions_baseline
252  end
253
254  config.before do |example|
255    if example.metadata.fetch(:stub_feature_flags, true)
256      # The following can be removed when we remove the staged rollout strategy
257      # and we can just enable it using instance wide settings
258      # (ie. ApplicationSetting#auto_devops_enabled)
259      stub_feature_flags(force_autodevops_on_by_default: false)
260
261      # Merge request widget GraphQL requests are disabled in the tests
262      # for now whilst we migrate as much as we can over the GraphQL
263      # stub_feature_flags(merge_request_widget_graphql: false)
264
265      # Using FortiAuthenticator as OTP provider is disabled by default in
266      # tests, until we introduce it in user settings
267      stub_feature_flags(forti_authenticator: false)
268
269      # Using FortiToken Cloud as OTP provider is disabled by default in
270      # tests, until we introduce it in user settings
271      stub_feature_flags(forti_token_cloud: false)
272
273      # Disable for now whilst we add more states
274      stub_feature_flags(restructured_mr_widget: false)
275
276      # These feature flag are by default disabled and used in disaster recovery mode
277      stub_feature_flags(ci_queueing_disaster_recovery_disable_fair_scheduling: false)
278      stub_feature_flags(ci_queueing_disaster_recovery_disable_quota: false)
279
280      enable_rugged = example.metadata[:enable_rugged].present?
281
282      # Disable Rugged features by default
283      Gitlab::Git::RuggedImpl::Repository::FEATURE_FLAGS.each do |flag|
284        stub_feature_flags(flag => enable_rugged)
285      end
286
287      # Disable the usage of file_identifier_hash by default until it is ready
288      # See https://gitlab.com/gitlab-org/gitlab/-/issues/33867
289      stub_feature_flags(file_identifier_hash: false)
290
291      stub_feature_flags(diffs_virtual_scrolling: false)
292
293      # The following `vue_issues_list`/`vue_issuables_list` stubs can be removed
294      # once the Vue issues page has feature parity with the current Haml page
295      stub_feature_flags(vue_issues_list: false)
296      stub_feature_flags(vue_issuables_list: false)
297
298      # Disable `refactor_blob_viewer` as we refactor
299      # the blob viewer. See the follwing epic for more:
300      # https://gitlab.com/groups/gitlab-org/-/epics/5531
301      stub_feature_flags(refactor_blob_viewer: false)
302
303      # Disable `main_branch_over_master` as we migrate
304      # from `master` to `main` accross our codebase.
305      # It's done in order to preserve the concistency in tests
306      # As we're ready to change `master` usages to `main`, let's enable it
307      stub_feature_flags(main_branch_over_master: false)
308
309      stub_feature_flags(issue_boards_filtered_search: false)
310
311      # Disable issue respositioning to avoid heavy load on database when importing big projects.
312      # This is only turned on when app is handling heavy project imports.
313      # Can be removed when we find a better way to deal with the problem.
314      # For more information check https://gitlab.com/gitlab-com/gl-infra/production/-/issues/4321
315      stub_feature_flags(block_issue_repositioning: false)
316
317      # These are ops feature flags that are disabled by default
318      stub_feature_flags(disable_anonymous_search: false)
319      stub_feature_flags(disable_anonymous_project_search: false)
320
321      # Disable the refactored top nav search until there is functionality
322      # Can be removed once all existing functionality has been replicated
323      # For more information check https://gitlab.com/gitlab-org/gitlab/-/issues/339348
324      stub_feature_flags(new_header_search: false)
325
326      allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(enable_rugged)
327    else
328      unstub_all_feature_flags
329    end
330
331    # Stub these calls due to being expensive operations
332    # It can be reenabled for specific tests via:
333    #
334    # expect(Gitlab::Git::KeepAround).to receive(:execute).and_call_original
335    allow(Gitlab::Git::KeepAround).to receive(:execute)
336
337    # Stub these calls due to being expensive operations
338    # It can be reenabled for specific tests via:
339    #
340    # expect(Gitlab::JobWaiter).to receive(:wait).and_call_original
341    allow_any_instance_of(Gitlab::JobWaiter).to receive(:wait)
342
343    Gitlab::ProcessMemoryCache.cache_backend.clear
344
345    Sidekiq::Worker.clear_all
346
347    # Administrators have to re-authenticate in order to access administrative
348    # functionality when application setting admin_mode is active. Any spec
349    # that requires administrative access can use the tag :enable_admin_mode
350    # to avoid the second auth step (provided the user is already an admin):
351    #
352    # context 'some test that requires admin mode', :enable_admin_mode do ... end
353    #
354    # Some specs do get admin mode enabled automatically (e.g. `spec/controllers/admin`).
355    # In this case, specs that need to test both admin mode states can use the
356    # :do_not_mock_admin_mode tag to disable auto admin mode.
357    #
358    # See also spec/support/helpers/admin_mode_helpers.rb
359    if example.metadata[:enable_admin_mode] && !example.metadata[:do_not_mock_admin_mode]
360      allow_any_instance_of(Gitlab::Auth::CurrentUserMode).to receive(:admin_mode?) do |current_user_mode|
361        current_user_mode.send(:user)&.admin?
362      end
363    end
364
365    # Make sure specs test by default admin mode setting on, unless forced to the opposite
366    stub_application_setting(admin_mode: true) unless example.metadata[:do_not_mock_admin_mode_setting]
367
368    allow(Gitlab::CurrentSettings).to receive(:current_application_settings?).and_return(false)
369  end
370
371  config.around(:example, :quarantine) do |example|
372    # Skip tests in quarantine unless we explicitly focus on them.
373    example.run if config.inclusion_filter[:quarantine]
374  end
375
376  config.around(:example, :request_store) do |example|
377    Gitlab::WithRequestStore.with_request_store { example.run }
378  end
379
380  # previous test runs may have left some resources throttled
381  config.before do
382    ::Gitlab::ExclusiveLease.reset_all!("el:throttle:*")
383  end
384
385  config.before(:example, :assume_throttled) do |example|
386    allow(::Gitlab::ExclusiveLease).to receive(:throttle).and_return(nil)
387  end
388
389  config.before(:example, :request_store) do
390    # Clear request store before actually starting the spec (the
391    # `around` above will have the request store enabled for all
392    # `before` blocks)
393    RequestStore.clear!
394  end
395
396  config.around do |example|
397    # Wrap each example in it's own context to make sure the contexts don't
398    # leak
399    Gitlab::ApplicationContext.with_raw_context { example.run }
400  end
401
402  config.around do |example|
403    with_sidekiq_server_middleware do |chain|
404      Gitlab::SidekiqMiddleware.server_configurator(
405        metrics: false, # The metrics don't go anywhere in tests
406        arguments_logger: false, # We're not logging the regular messages for inline jobs
407        memory_killer: false # This is not a thing we want to do inline in tests
408      ).call(chain)
409      chain.add DisableQueryLimit
410      chain.insert_after ::Gitlab::SidekiqMiddleware::RequestStoreMiddleware, IsolatedRequestStore
411
412      example.run
413    end
414  end
415
416  config.after do
417    Fog.unmock! if Fog.mock?
418    Gitlab::CurrentSettings.clear_in_memory_application_settings!
419
420    # Reset all feature flag stubs to default for testing
421    stub_all_feature_flags
422
423    # Re-enable query limiting in case it was disabled
424    Gitlab::QueryLimiting.enable!
425  end
426
427  config.before(:example, :mailer) do
428    reset_delivered_emails!
429  end
430
431  config.before(:example, :prometheus) do
432    matching_files = File.join(::Prometheus::Client.configuration.multiprocess_files_dir, "**/*.db")
433    Dir[matching_files].map { |filename| File.delete(filename) if File.file?(filename) }
434
435    Gitlab::Metrics.reset_registry!
436  end
437
438  config.before(:example, :eager_load) do
439    Rails.application.eager_load!
440  end
441
442  # This makes sure the `ApplicationController#can?` method is stubbed with the
443  # original implementation for all view specs.
444  config.before(:each, type: :view) do
445    allow(view).to receive(:can?) do |*args|
446      Ability.allowed?(*args)
447    end
448  end
449
450  # Allows stdout to be redirected to reduce noise
451  config.before(:each, :silence_stdout) do
452    $stdout = StringIO.new
453  end
454
455  # Makes diffs show entire non-truncated values.
456  config.before(:each, unlimited_max_formatted_output_length: true) do |_example|
457    config.expect_with :rspec do |c|
458      c.max_formatted_output_length = nil
459    end
460  end
461
462  config.after(:each, :silence_stdout) do
463    $stdout = STDOUT
464  end
465
466  config.disable_monkey_patching!
467end
468
469ActiveRecord::Migration.maintain_test_schema!
470
471Shoulda::Matchers.configure do |config|
472  config.integrate do |with|
473    with.test_framework :rspec
474    with.library :rails
475  end
476end
477
478# Prevent Rugged from picking up local developer gitconfig.
479Rugged::Settings['search_path_global'] = Rails.root.join('tmp/tests').to_s
480
481# Initialize FactoryDefault to use create_default helper
482TestProf::FactoryDefault.init
483
484# Exclude the Geo proxy API request from getting on_next_request Warden handlers,
485# necessary to prevent race conditions with feature tests not getting authenticated.
486::Warden.asset_paths << %r{^/api/v4/geo/proxy$}
487
488module TouchRackUploadedFile
489  def initialize_from_file_path(path)
490    super
491
492    # This is a no-op workaround for https://github.com/docker/for-linux/issues/1015
493    File.utime @tempfile.atime, @tempfile.mtime, @tempfile.path # rubocop:disable Gitlab/ModuleWithInstanceVariables
494  end
495end
496
497Rack::Test::UploadedFile.prepend(TouchRackUploadedFile)
498