2017-09-10 17:25:29 +05:30
|
|
|
require 'spec_helper'
|
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
# rubocop:disable RSpec/FactoriesInMigrationSpecs
|
2018-03-17 18:26:18 +05:30
|
|
|
describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :migration, schema: 20171114162227 do
|
2018-12-05 23:21:45 +05:30
|
|
|
include GitHelpers
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
let(:merge_request_diffs) { table(:merge_request_diffs) }
|
|
|
|
let(:merge_requests) { table(:merge_requests) }
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
describe '#perform' do
|
2018-03-17 18:26:18 +05:30
|
|
|
let(:project) { create(:project, :repository) }
|
|
|
|
let(:merge_request) { merge_requests.create!(iid: 1, target_project_id: project.id, source_project_id: project.id, target_branch: 'feature', source_branch: 'master').becomes(MergeRequest) }
|
|
|
|
let(:merge_request_diff) { MergeRequest.find(merge_request.id).create_merge_request_diff }
|
2017-09-10 17:25:29 +05:30
|
|
|
let(:updated_merge_request_diff) { MergeRequestDiff.find(merge_request_diff.id) }
|
2018-12-05 23:21:45 +05:30
|
|
|
let(:rugged) { rugged_repo(project.repository) }
|
2017-09-10 17:25:29 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
before do
|
|
|
|
allow_any_instance_of(MergeRequestDiff)
|
|
|
|
.to receive(:commits_count=).and_return(nil)
|
|
|
|
end
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
def diffs_to_hashes(diffs)
|
|
|
|
diffs.as_json(only: Gitlab::Git::Diff::SERIALIZE_KEYS).map(&:with_indifferent_access)
|
|
|
|
end
|
|
|
|
|
|
|
|
def quote_yaml(value)
|
|
|
|
MergeRequestDiff.connection.quote(YAML.dump(value))
|
|
|
|
end
|
|
|
|
|
|
|
|
def convert_to_yaml(merge_request_diff_id, commits, diffs)
|
|
|
|
MergeRequestDiff.where(id: merge_request_diff_id).update_all(
|
|
|
|
"st_commits = #{quote_yaml(commits)}, st_diffs = #{quote_yaml(diffs)}"
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
shared_examples 'updated MR diff' do
|
|
|
|
before do
|
|
|
|
convert_to_yaml(merge_request_diff.id, commits, diffs)
|
|
|
|
|
|
|
|
MergeRequestDiffCommit.delete_all
|
|
|
|
MergeRequestDiffFile.delete_all
|
|
|
|
|
|
|
|
subject.perform(merge_request_diff.id, merge_request_diff.id)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'creates correct entries in the merge_request_diff_commits table' do
|
2018-03-17 18:26:18 +05:30
|
|
|
expect(updated_merge_request_diff.merge_request_diff_commits.count).to eq(expected_commits.count)
|
|
|
|
expect(updated_merge_request_diff.commits.map(&:to_hash)).to eq(expected_commits)
|
2017-09-10 17:25:29 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'creates correct entries in the merge_request_diff_files table' do
|
|
|
|
expect(updated_merge_request_diff.merge_request_diff_files.count).to eq(expected_diffs.count)
|
|
|
|
expect(diffs_to_hashes(updated_merge_request_diff.raw_diffs)).to eq(expected_diffs)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'sets the st_commits and st_diffs columns to nil' do
|
|
|
|
expect(updated_merge_request_diff.st_commits_before_type_cast).to be_nil
|
|
|
|
expect(updated_merge_request_diff.st_diffs_before_type_cast).to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the diff IDs passed do not exist' do
|
|
|
|
it 'does not raise' do
|
|
|
|
expect { subject.perform(0, 0) }.not_to raise_exception
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the merge request diff has no serialised commits or diffs' do
|
|
|
|
before do
|
|
|
|
merge_request_diff.update(st_commits: nil, st_diffs: nil)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not raise' do
|
|
|
|
expect { subject.perform(merge_request_diff.id, merge_request_diff.id) }
|
|
|
|
.not_to raise_exception
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'processing multiple merge request diffs' do
|
|
|
|
let(:start_id) { described_class::MergeRequestDiff.minimum(:id) }
|
|
|
|
let(:stop_id) { described_class::MergeRequestDiff.maximum(:id) }
|
|
|
|
|
|
|
|
before do
|
2018-03-17 18:26:18 +05:30
|
|
|
merge_request.create_merge_request_diff
|
2017-09-10 17:25:29 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
convert_to_yaml(start_id, merge_request_diff.commits, diffs_to_hashes(merge_request_diff.merge_request_diff_files))
|
|
|
|
convert_to_yaml(stop_id, updated_merge_request_diff.commits, diffs_to_hashes(updated_merge_request_diff.merge_request_diff_files))
|
2017-09-10 17:25:29 +05:30
|
|
|
|
|
|
|
MergeRequestDiffCommit.delete_all
|
|
|
|
MergeRequestDiffFile.delete_all
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when BUFFER_ROWS is exceeded' do
|
|
|
|
before do
|
|
|
|
stub_const("#{described_class}::BUFFER_ROWS", 1)
|
2018-03-17 18:26:18 +05:30
|
|
|
|
|
|
|
allow(Gitlab::Database).to receive(:bulk_insert).and_call_original
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'inserts commit rows in chunks of BUFFER_ROWS' do
|
|
|
|
# There are 29 commits in each diff, so we should have slices of 20 + 9 + 20 + 9.
|
|
|
|
stub_const("#{described_class}::BUFFER_ROWS", 20)
|
|
|
|
|
|
|
|
expect(Gitlab::Database).to receive(:bulk_insert)
|
|
|
|
.with('merge_request_diff_commits', anything)
|
|
|
|
.exactly(4)
|
|
|
|
.times
|
|
|
|
.and_call_original
|
|
|
|
|
|
|
|
subject.perform(start_id, stop_id)
|
2017-09-10 17:25:29 +05:30
|
|
|
end
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
it 'inserts diff rows in chunks of DIFF_FILE_BUFFER_ROWS' do
|
|
|
|
# There are 20 files in each diff, so we should have slices of 20 + 20.
|
|
|
|
stub_const("#{described_class}::DIFF_FILE_BUFFER_ROWS", 20)
|
|
|
|
|
|
|
|
expect(Gitlab::Database).to receive(:bulk_insert)
|
|
|
|
.with('merge_request_diff_files', anything)
|
|
|
|
.exactly(2)
|
|
|
|
.times
|
|
|
|
.and_call_original
|
2017-09-10 17:25:29 +05:30
|
|
|
|
|
|
|
subject.perform(start_id, stop_id)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when BUFFER_ROWS is not exceeded' do
|
|
|
|
it 'only updates once' do
|
2018-03-17 18:26:18 +05:30
|
|
|
expect(Gitlab::Database).to receive(:bulk_insert)
|
|
|
|
.with('merge_request_diff_commits', anything)
|
|
|
|
.once
|
|
|
|
.and_call_original
|
|
|
|
|
|
|
|
expect(Gitlab::Database).to receive(:bulk_insert)
|
|
|
|
.with('merge_request_diff_files', anything)
|
|
|
|
.once
|
|
|
|
.and_call_original
|
2017-09-10 17:25:29 +05:30
|
|
|
|
|
|
|
subject.perform(start_id, stop_id)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
context 'when some rows were already inserted due to a previous failure' do
|
|
|
|
before do
|
|
|
|
subject.perform(start_id, stop_id)
|
2017-09-10 17:25:29 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
convert_to_yaml(start_id, merge_request_diff.commits, diffs_to_hashes(merge_request_diff.merge_request_diff_files))
|
|
|
|
convert_to_yaml(stop_id, updated_merge_request_diff.commits, diffs_to_hashes(updated_merge_request_diff.merge_request_diff_files))
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not raise' do
|
|
|
|
expect { subject.perform(start_id, stop_id) }.not_to raise_exception
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'logs a message' do
|
|
|
|
expect(Rails.logger).to receive(:info)
|
|
|
|
.with(
|
|
|
|
a_string_matching(described_class.name).and(matching([start_id, stop_id].inspect))
|
|
|
|
)
|
|
|
|
.twice
|
|
|
|
|
|
|
|
subject.perform(start_id, stop_id)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'ends up with the correct rows' do
|
|
|
|
expect(updated_merge_request_diff.commits.count).to eq(29)
|
|
|
|
expect(updated_merge_request_diff.raw_diffs.count).to eq(20)
|
|
|
|
end
|
2017-09-10 17:25:29 +05:30
|
|
|
end
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
context 'when the merge request diff update fails' do
|
|
|
|
let(:exception) { ActiveRecord::RecordNotFound }
|
|
|
|
|
|
|
|
let(:perform_ignoring_exceptions) do
|
2019-07-07 11:18:12 +05:30
|
|
|
subject.perform(start_id, stop_id)
|
|
|
|
rescue described_class::Error
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
before do
|
|
|
|
allow_any_instance_of(described_class::MergeRequestDiff::ActiveRecord_Relation)
|
|
|
|
.to receive(:update_all).and_raise(exception)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'raises an error' do
|
|
|
|
expect { subject.perform(start_id, stop_id) }
|
|
|
|
.to raise_exception(described_class::Error)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'logs the error' do
|
|
|
|
expect(Rails.logger).to receive(:info).with(
|
|
|
|
a_string_matching(described_class.name)
|
|
|
|
.and(matching([start_id, stop_id].inspect))
|
|
|
|
.and(matching(exception.name))
|
|
|
|
)
|
|
|
|
|
|
|
|
perform_ignoring_exceptions
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'still adds diff commits' do
|
|
|
|
expect { perform_ignoring_exceptions }
|
|
|
|
.to change { MergeRequestDiffCommit.count }
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'still adds diff files' do
|
|
|
|
expect { perform_ignoring_exceptions }
|
|
|
|
.to change { MergeRequestDiffFile.count }
|
|
|
|
end
|
2017-09-10 17:25:29 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the merge request diff has valid commits and diffs' do
|
|
|
|
let(:commits) { merge_request_diff.commits.map(&:to_hash) }
|
2018-03-17 18:26:18 +05:30
|
|
|
let(:expected_commits) { commits }
|
|
|
|
let(:diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) }
|
|
|
|
let(:expected_diffs) { diffs }
|
|
|
|
|
|
|
|
include_examples 'updated MR diff'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the merge request diff has diffs but no commits' do
|
|
|
|
let(:commits) { nil }
|
|
|
|
let(:expected_commits) { [] }
|
2017-09-10 17:25:29 +05:30
|
|
|
let(:diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) }
|
|
|
|
let(:expected_diffs) { diffs }
|
|
|
|
|
|
|
|
include_examples 'updated MR diff'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the merge request diffs do not have too_large set' do
|
|
|
|
let(:commits) { merge_request_diff.commits.map(&:to_hash) }
|
2018-03-17 18:26:18 +05:30
|
|
|
let(:expected_commits) { commits }
|
2017-09-10 17:25:29 +05:30
|
|
|
let(:expected_diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) }
|
|
|
|
|
|
|
|
let(:diffs) do
|
|
|
|
expected_diffs.map { |diff| diff.except(:too_large) }
|
|
|
|
end
|
|
|
|
|
|
|
|
include_examples 'updated MR diff'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the merge request diffs do not have a_mode and b_mode set' do
|
|
|
|
let(:commits) { merge_request_diff.commits.map(&:to_hash) }
|
2018-03-17 18:26:18 +05:30
|
|
|
let(:expected_commits) { commits }
|
2017-09-10 17:25:29 +05:30
|
|
|
let(:expected_diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) }
|
|
|
|
|
|
|
|
let(:diffs) do
|
|
|
|
expected_diffs.map { |diff| diff.except(:a_mode, :b_mode) }
|
|
|
|
end
|
|
|
|
|
|
|
|
include_examples 'updated MR diff'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the merge request diffs have binary content' do
|
|
|
|
let(:commits) { merge_request_diff.commits.map(&:to_hash) }
|
2018-03-17 18:26:18 +05:30
|
|
|
let(:expected_commits) { commits }
|
2017-09-10 17:25:29 +05:30
|
|
|
let(:expected_diffs) { diffs }
|
|
|
|
|
|
|
|
# The start of a PDF created by Illustrator
|
|
|
|
let(:binary_string) do
|
|
|
|
"\x25\x50\x44\x46\x2d\x31\x2e\x35\x0d\x25\xe2\xe3\xcf\xd3\x0d\x0a".force_encoding(Encoding::BINARY)
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:diffs) do
|
|
|
|
[
|
|
|
|
{
|
|
|
|
'diff' => binary_string,
|
|
|
|
'new_path' => 'path',
|
|
|
|
'old_path' => 'path',
|
|
|
|
'a_mode' => '100644',
|
|
|
|
'b_mode' => '100644',
|
|
|
|
'new_file' => false,
|
|
|
|
'renamed_file' => false,
|
|
|
|
'deleted_file' => false,
|
|
|
|
'too_large' => false
|
|
|
|
}
|
|
|
|
]
|
|
|
|
end
|
|
|
|
|
|
|
|
include_examples 'updated MR diff'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the merge request diff has commits, but no diffs' do
|
|
|
|
let(:commits) { merge_request_diff.commits.map(&:to_hash) }
|
2018-03-17 18:26:18 +05:30
|
|
|
let(:expected_commits) { commits }
|
2017-09-10 17:25:29 +05:30
|
|
|
let(:diffs) { [] }
|
|
|
|
let(:expected_diffs) { diffs }
|
|
|
|
|
|
|
|
include_examples 'updated MR diff'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the merge request diffs have invalid content' do
|
|
|
|
let(:commits) { merge_request_diff.commits.map(&:to_hash) }
|
2018-03-17 18:26:18 +05:30
|
|
|
let(:expected_commits) { commits }
|
2017-09-10 17:25:29 +05:30
|
|
|
let(:diffs) { ['--broken-diff'] }
|
|
|
|
let(:expected_diffs) { [] }
|
|
|
|
|
|
|
|
include_examples 'updated MR diff'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the merge request diffs are Rugged::Patch instances' do
|
|
|
|
let(:commits) { merge_request_diff.commits.map(&:to_hash) }
|
2018-03-17 18:26:18 +05:30
|
|
|
let(:first_commit) { project.repository.commit(merge_request_diff.head_commit_sha) }
|
|
|
|
let(:expected_commits) { commits }
|
2018-11-18 11:00:15 +05:30
|
|
|
let(:diffs) { rugged_diff(first_commit.sha).patches }
|
2017-09-10 17:25:29 +05:30
|
|
|
let(:expected_diffs) { [] }
|
|
|
|
|
|
|
|
include_examples 'updated MR diff'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the merge request diffs are Rugged::Diff::Delta instances' do
|
|
|
|
let(:commits) { merge_request_diff.commits.map(&:to_hash) }
|
2018-03-17 18:26:18 +05:30
|
|
|
let(:first_commit) { project.repository.commit(merge_request_diff.head_commit_sha) }
|
|
|
|
let(:expected_commits) { commits }
|
2018-11-18 11:00:15 +05:30
|
|
|
let(:diffs) { rugged_diff(first_commit.sha).deltas }
|
2017-09-10 17:25:29 +05:30
|
|
|
let(:expected_diffs) { [] }
|
|
|
|
|
|
|
|
include_examples 'updated MR diff'
|
|
|
|
end
|
2018-11-18 11:00:15 +05:30
|
|
|
|
|
|
|
def rugged_diff(commit_sha)
|
|
|
|
rugged_commit = rugged.lookup(commit_sha)
|
|
|
|
rugged_commit.parents[0].diff(rugged_commit)
|
|
|
|
end
|
2017-09-10 17:25:29 +05:30
|
|
|
end
|
|
|
|
end
|
2019-03-02 22:35:43 +05:30
|
|
|
# rubocop:enable RSpec/FactoriesInMigrationSpecs
|