test_env.rb 11.4 KB
Newer Older
D
Dmitriy Zaporozhets 已提交
1
require 'rspec/mocks'
K
Ken 已提交
2
require 'toml-rb'
D
Dmitriy Zaporozhets 已提交
3

4 5 6
module TestEnv
  extend self

7 8
  ComponentFailedToInstallError = Class.new(StandardError)

C
Ciro Santilli 已提交
9 10
  # When developing the seed repository, comment out the branch you will modify.
  BRANCH_SHA = {
A
Alexis Reigel 已提交
11
    'signed-commits'                     => '2d1096e',
12 13
    'not-merged-branch'                  => 'b83d6e3',
    'branch-merged'                      => '498214d',
S
Sean McGivern 已提交
14
    'empty-branch'                       => '7efb185',
S
Sean McGivern 已提交
15
    'ends-with.json'                     => '98b0d8b',
S
Sean McGivern 已提交
16 17 18 19 20
    'flatten-dir'                        => 'e56497b',
    'feature'                            => '0b4bc9a',
    'feature_conflict'                   => 'bb5206f',
    'fix'                                => '48f0be4',
    'improve/awesome'                    => '5937ac0',
21
    'merged-target'                      => '21751bf',
S
Sean McGivern 已提交
22
    'markdown'                           => '0ed8c6c',
23
    'lfs'                                => '55bc176',
24
    'master'                             => 'b83d6e3',
S
Stan Hu 已提交
25
    'merge-test'                         => '5937ac0',
S
Sean McGivern 已提交
26 27 28 29 30
    "'test'"                             => 'e56497b',
    'orphaned-branch'                    => '45127a9',
    'binary-encoding'                    => '7b1cf43',
    'gitattributes'                      => '5a62481',
    'expand-collapse-diffs'              => '4842455',
31
    'symlink-expand-diff'                => '81e6355',
S
Sean McGivern 已提交
32 33 34
    'expand-collapse-files'              => '025db92',
    'expand-collapse-lines'              => '238e82d',
    'video'                              => '8879059',
L
Luke "Jared" Bennett 已提交
35
    'add-balsamiq-file'                  => 'b89b56d',
S
Sean McGivern 已提交
36
    'crlf-diff'                          => '5938907',
S
Sean McGivern 已提交
37
    'conflict-start'                     => '824be60',
S
Sean McGivern 已提交
38 39
    'conflict-resolvable'                => '1450cd6',
    'conflict-binary-file'               => '259a6fb',
S
Sean McGivern 已提交
40
    'conflict-contains-conflict-markers' => '78a3086',
S
Sean McGivern 已提交
41
    'conflict-missing-side'              => 'eb227b3',
42
    'conflict-non-utf8'                  => 'd0a293c',
S
Sean McGivern 已提交
43
    'conflict-too-large'                 => '39fa04f',
J
jurre 已提交
44
    'deleted-image-test'                 => '6c17798',
S
Sean McGivern 已提交
45
    'wip'                                => 'b9238ee',
46
    'csv'                                => '3dd0896',
47
    'v1.1.0'                             => 'b83d6e3',
P
Phil Hughes 已提交
48
    'add-ipython-files'                  => '93ee732',
49
    'add-pdf-file'                       => 'e774ebd',
F
Felipe Artur 已提交
50 51
    'add-pdf-text-binary'                => '79faa7b',
    'add_images_and_changes'             => '010d106'
D
Douwe Maan 已提交
52
  }.freeze
53

54 55 56 57
  # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
  # need to keep all the branches in sync.
  # We currently only need a subset of the branches
  FORKED_BRANCH_SHA = {
S
Sean McGivern 已提交
58 59 60 61
    'add-submodule-version-bump' => '3f547c0',
    'master'                     => '5937ac0',
    'remove-submodule'           => '2a33e0c',
    'conflict-resolvable-fork'   => '404fa3f'
D
Douwe Maan 已提交
62
  }.freeze
63

64
  TMP_TEST_PATH = Rails.root.join('tmp', 'tests', '**')
65
  REPOS_STORAGE = 'default'.freeze
66

67 68
  # Test environment
  #
69
  # See gitlab.yml.example test section for paths
70
  #
71
  def init(opts = {})
72 73 74 75 76
    unless Rails.env.test?
      puts "\nTestEnv.init can only be run if `RAILS_ENV` is set to 'test' not '#{Rails.env}'!\n"
      exit 1
    end

D
Dmitriy Zaporozhets 已提交
77 78 79
    # Disable mailer for spinach tests
    disable_mailer if opts[:mailer] == false

R
Robert Speicher 已提交
80
    clean_test_path
81

82 83 84
    # Setup GitLab shell for test instance
    setup_gitlab_shell

85
    setup_gitaly
86

87
    # Create repository for FactoryBot.create(:project)
88
    setup_factory_repo
89

90
    # Create repository for FactoryBot.create(:forked_project_with_submodules)
91
    setup_forked_repo
I
Izaak Alpert 已提交
92 93
  end

94
  def disable_mailer
95 96
    allow_any_instance_of(NotificationService).to receive(:mailer)
      .and_return(double.as_null_object)
I
Izaak Alpert 已提交
97
  end
D
Dmitriy Zaporozhets 已提交
98

99
  def enable_mailer
100 101
    allow_any_instance_of(NotificationService).to receive(:mailer)
      .and_call_original
I
Izaak Alpert 已提交
102 103
  end

104
  def disable_pre_receive
105
    allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
106 107
  end

R
Robert Speicher 已提交
108 109 110 111
  # Clean /tmp/tests
  #
  # Keeps gitlab-shell and gitlab-test
  def clean_test_path
112
    Dir[TMP_TEST_PATH].each do |entry|
113
      unless File.basename(entry) =~ /\A(gitaly|gitlab-(shell|test|test_bare|test-fork|test-fork_bare))\z/
R
Robert Speicher 已提交
114 115 116
        FileUtils.rm_rf(entry)
      end
    end
117 118 119 120

    FileUtils.mkdir_p(repos_path)
    FileUtils.mkdir_p(backup_path)
    FileUtils.mkdir_p(pages_path)
Z
Zeger-Jan van de Weg 已提交
121
    FileUtils.mkdir_p(artifacts_path)
R
Robert Speicher 已提交
122 123
  end

124 125 126 127 128 129 130 131
  def clean_gitlab_test_path
    Dir[TMP_TEST_PATH].each do |entry|
      if File.basename(entry) =~ /\A(gitlab-(test|test_bare|test-fork|test-fork_bare))\z/
        FileUtils.rm_rf(entry)
      end
    end
  end

132
  def setup_gitlab_shell
133 134 135 136
    component_timed_setup('GitLab Shell',
      install_dir: Gitlab.config.gitlab_shell.path,
      version: Gitlab::Shell.version_required,
      task: 'gitlab:shell:install')
I
Izaak Alpert 已提交
137
  end
138

139
  def setup_gitaly
J
Jacob Vosmaer 已提交
140
    socket_path = Gitlab::GitalyClient.address('default').sub(/\Aunix:/, '')
141
    gitaly_dir = File.dirname(socket_path)
142

143 144 145 146
    component_timed_setup('Gitaly',
      install_dir: gitaly_dir,
      version: Gitlab::GitalyClient.expected_server_version,
      task: "gitlab:gitaly:install[#{gitaly_dir}]") do
147

148 149 150
      # Always re-create config, in case it's outdated. This is fast anyway.
      Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, force: true)

151
      start_gitaly(gitaly_dir)
152
    end
153 154
  end

155
  def start_gitaly(gitaly_dir)
J
Jacob Vosmaer 已提交
156 157 158 159 160 161
    if ENV['CI'].present?
      # Gitaly has been spawned outside this process already
      return
    end

    spawn_script = Rails.root.join('scripts/gitaly-test-spawn').to_s
162 163 164 165 166
    Bundler.with_original_env do
      raise "gitaly spawn failed" unless system(spawn_script)
    end
    @gitaly_pid = Integer(File.read('tmp/tests/gitaly.pid'))

167 168
    Kernel.at_exit { stop_gitaly }

169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
    wait_gitaly
  end

  def wait_gitaly
    sleep_time = 10
    sleep_interval = 0.1
    socket = Gitlab::GitalyClient.address('default').sub('unix:', '')

    Integer(sleep_time / sleep_interval).times do
      begin
        Socket.unix(socket)
        return
      rescue
        sleep sleep_interval
      end
    end

    raise "could not connect to gitaly at #{socket.inspect} after #{sleep_time} seconds"
187 188 189 190 191 192
  end

  def stop_gitaly
    return unless @gitaly_pid

    Process.kill('KILL', @gitaly_pid)
193 194
  rescue Errno::ESRCH
    # The process can already be gone if the test run was INTerrupted.
195 196
  end

197
  def setup_factory_repo
198 199 200 201 202 203 204 205 206 207 208
    setup_repo(factory_repo_path, factory_repo_path_bare, factory_repo_name,
               BRANCH_SHA)
  end

  # This repo has a submodule commit that is not present in the main test
  # repository.
  def setup_forked_repo
    setup_repo(forked_repo_path, forked_repo_path_bare, forked_repo_name,
               FORKED_BRANCH_SHA)
  end

209
  def setup_repo(repo_path, repo_path_bare, repo_name, refs)
210
    clone_url = "https://gitlab.com/gitlab-org/#{repo_name}.git"
I
Izaak Alpert 已提交
211

212
    unless File.directory?(repo_path)
213
      system(*%W(#{Gitlab.config.git.bin_path} clone -q #{clone_url} #{repo_path}))
C
Ciro Santilli 已提交
214 215
    end

216
    set_repo_refs(repo_path, refs)
C
Ciro Santilli 已提交
217

218 219 220 221
    unless File.directory?(repo_path_bare)
      # We must copy bare repositories because we will push to them.
      system(git_env, *%W(#{Gitlab.config.git.bin_path} clone -q --bare #{repo_path} #{repo_path_bare}))
    end
222 223
  end

224
  def copy_repo(project, bare_repo:, refs:)
225 226
    target_repo_path = File.expand_path(repos_path + "/#{project.disk_path}.git")

D
Dmitriy Zaporozhets 已提交
227
    FileUtils.mkdir_p(target_repo_path)
228
    FileUtils.cp_r("#{File.expand_path(bare_repo)}/.", target_repo_path)
D
Dmitriy Zaporozhets 已提交
229
    FileUtils.chmod_R 0755, target_repo_path
230
    set_repo_refs(target_repo_path, refs)
231 232
  end

233
  def repos_path
234
    @repos_path ||= Gitlab.config.repositories.storages[REPOS_STORAGE].legacy_disk_path
235
  end
C
Ciro Santilli 已提交
236

237 238 239 240
  def backup_path
    Gitlab.config.backup.path
  end

241 242 243 244
  def pages_path
    Gitlab.config.pages.path
  end

Z
Zeger-Jan van de Weg 已提交
245
  def artifacts_path
246
    Gitlab.config.artifacts.storage_path
Z
Zeger-Jan van de Weg 已提交
247 248
  end

249 250 251 252
  # When no cached assets exist, manually hit the root path to create them
  #
  # Otherwise they'd be created by the first test, often timing out and
  # causing a transient test failure
253
  def eager_load_driver_server
254 255
    return unless defined?(Capybara)

S
Stan Hu 已提交
256
    puts "Starting the Capybara driver server..."
257
    Capybara.current_session.visit '/'
258 259
  end

260 261 262 263 264 265 266 267
  def factory_repo_path_bare
    "#{factory_repo_path}_bare"
  end

  def forked_repo_path_bare
    "#{forked_repo_path}_bare"
  end

268 269 270 271 272 273 274 275
  def with_empty_bare_repository(name = nil)
    path = Rails.root.join('tmp/tests', name || 'empty-bare-repository').to_s

    yield(Rugged::Repository.init_at(path, :bare))
  ensure
    FileUtils.rm_rf(path)
  end

C
Ciro Santilli 已提交
276 277 278
  private

  def factory_repo_path
C
Ciro Santilli 已提交
279 280 281
    @factory_repo_path ||= Rails.root.join('tmp', 'tests', factory_repo_name)
  end

C
Ciro Santilli 已提交
282 283 284
  def factory_repo_name
    'gitlab-test'
  end
285

286 287 288 289 290 291 292 293
  def forked_repo_path
    @forked_repo_path ||= Rails.root.join('tmp', 'tests', forked_repo_name)
  end

  def forked_repo_name
    'gitlab-test-fork'
  end

294 295 296
  # Prevent developer git configurations from being persisted to test
  # repositories
  def git_env
297
    { 'GIT_TEMPLATE_DIR' => '' }
298
  end
S
Sean McGivern 已提交
299 300

  def set_repo_refs(repo_path, branch_sha)
301
    instructions = branch_sha.map { |branch, sha| "update refs/heads/#{branch}\x00#{sha}\x00" }.join("\x00") << "\x00"
302 303
    update_refs = %W(#{Gitlab.config.git.bin_path} update-ref --stdin -z)
    reset = proc do
304 305 306 307
      Dir.chdir(repo_path) do
        IO.popen(update_refs, "w") { |io| io.write(instructions) }
        $?.success?
      end
308 309
    end

310 311 312 313 314 315
    # Try to reset without fetching to avoid using the network.
    unless reset.call
      raise 'Could not fetch test seed repository.' unless system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} fetch origin))

      # Before we used Git clone's --mirror option, bare repos could end up
      # with missing refs, clearing them and retrying should fix the issue.
316
      clean_gitlab_test_path && init unless reset.call
S
Sean McGivern 已提交
317 318
    end
  end
319

320 321 322 323 324 325
  def component_timed_setup(component, install_dir:, version:, task:)
    puts "\n==> Setting up #{component}..."
    start = Time.now

    ensure_component_dir_name_is_correct!(component, install_dir)

326 327 328
    # On CI, once installed, components never need update
    return if File.exist?(install_dir) && ENV['CI']

329 330 331
    if component_needs_update?(install_dir, version)
      # Cleanup the component entirely to ensure we start fresh
      FileUtils.rm_rf(install_dir)
332

333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
      unless system('rake', task)
        raise ComponentFailedToInstallError
      end
    end

    yield if block_given?

  rescue ComponentFailedToInstallError
    puts "\n#{component} failed to install, cleaning up #{install_dir}!\n"
    FileUtils.rm_rf(install_dir)
    exit 1
  ensure
    puts "    #{component} setup in #{Time.now - start} seconds...\n"
  end

  def ensure_component_dir_name_is_correct!(component, path)
    actual_component_dir_name = File.basename(path)
    expected_component_dir_name = component.parameterize

    unless actual_component_dir_name == expected_component_dir_name
      puts "    #{component} install dir should be named '#{expected_component_dir_name}', not '#{actual_component_dir_name}' (full install path given was '#{path}')!\n"
      exit 1
    end
  end

358
  def component_needs_update?(component_folder, expected_version)
359 360 361
    # Allow local overrides of the component for tests during development
    return false if Rails.env.test? && File.symlink?(component_folder)

362
    version = File.read(File.join(component_folder, 'VERSION')).strip
363 364 365 366

    # Notice that this will always yield true when using branch versions
    # (`=branch_name`), but that actually makes sure the server is always based
    # on the latest branch revision.
367
    version != expected_version
368 369 370
  rescue Errno::ENOENT
    true
  end
371
end