gitlab-org--gitlab-foss/spec/lib/gitlab/git/diff_spec.rb
Sean McGivern 9a73b634ab Add table for files in merge request diffs
This adds an ID-less table containing one row per file, per merge request
diff. It has a column for each attribute on Gitlab::Git::Diff that is serialised
currently, with the advantage that we can easily query the attributes of this
new table.

It does not migrate existing data, so we have fallback code when the legacy
st_diffs column is present instead. For a merge request diff to be valid, it
should have at most one of:

* Rows in this new table, with the correct merge_request_diff_id.
* A non-NULL st_diffs column.

It may have neither, if the diff is empty.
2017-06-16 18:30:01 +01:00

354 lines
9.7 KiB
Ruby

require "spec_helper"
describe Gitlab::Git::Diff, seed_helper: true do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
before do
@raw_diff_hash = {
diff: <<EOT.gsub(/^ {8}/, "").sub(/\n$/, ""),
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,6 @@
[submodule "gitlab-shell"]
\tpath = gitlab-shell
\turl = https://github.com/gitlabhq/gitlab-shell.git
+[submodule "gitlab-grack"]
+ path = gitlab-grack
+ url = https://gitlab.com/gitlab-org/gitlab-grack.git
EOT
new_path: ".gitmodules",
old_path: ".gitmodules",
a_mode: '100644',
b_mode: '100644',
new_file: false,
renamed_file: false,
deleted_file: false,
too_large: false
}
@rugged_diff = repository.rugged.diff("5937ac0a7beb003549fc5fd26fc247adbce4a52e^", "5937ac0a7beb003549fc5fd26fc247adbce4a52e", paths:
[".gitmodules"]).patches.first
end
describe 'size limit feature toggles' do
context 'when the feature gitlab_git_diff_size_limit_increase is enabled' do
before do
Feature.enable('gitlab_git_diff_size_limit_increase')
end
it 'returns 200 KB for size_limit' do
expect(described_class.size_limit).to eq(200.kilobytes)
end
it 'returns 100 KB for collapse_limit' do
expect(described_class.collapse_limit).to eq(100.kilobytes)
end
end
context 'when the feature gitlab_git_diff_size_limit_increase is disabled' do
before do
Feature.disable('gitlab_git_diff_size_limit_increase')
end
it 'returns 100 KB for size_limit' do
expect(described_class.size_limit).to eq(100.kilobytes)
end
it 'returns 10 KB for collapse_limit' do
expect(described_class.collapse_limit).to eq(10.kilobytes)
end
end
end
describe '.new' do
context 'using a Hash' do
context 'with a small diff' do
let(:diff) { described_class.new(@raw_diff_hash) }
it 'initializes the diff' do
expect(diff.to_hash).to eq(@raw_diff_hash)
end
it 'does not prune the diff' do
expect(diff).not_to be_too_large
end
end
context 'using a diff that is too large' do
it 'prunes the diff' do
diff = described_class.new(diff: 'a' * (described_class.size_limit + 1))
expect(diff.diff).to be_empty
expect(diff).to be_too_large
end
end
end
context 'using a Rugged::Patch' do
context 'with a small diff' do
let(:diff) { described_class.new(@rugged_diff) }
it 'initializes the diff' do
expect(diff.to_hash).to eq(@raw_diff_hash)
end
it 'does not prune the diff' do
expect(diff).not_to be_too_large
end
end
context 'using a diff that is too large' do
it 'prunes the diff' do
expect_any_instance_of(String).to receive(:bytesize).
and_return(1024 * 1024 * 1024)
diff = described_class.new(@rugged_diff)
expect(diff.diff).to be_empty
expect(diff).to be_too_large
end
end
context 'using a collapsable diff that is too large' do
before do
# The patch total size is 200, with lines between 21 and 54.
# This is a quick-and-dirty way to test this. Ideally, a new patch is
# added to the test repo with a size that falls between the real limits.
allow(Gitlab::Git::Diff).to receive(:size_limit).and_return(150)
allow(Gitlab::Git::Diff).to receive(:collapse_limit).and_return(100)
end
it 'prunes the diff as a large diff instead of as a collapsed diff' do
diff = described_class.new(@rugged_diff, expanded: false)
expect(diff.diff).to be_empty
expect(diff).to be_too_large
expect(diff).not_to be_collapsed
end
end
context 'using a large binary diff' do
it 'does not prune the diff' do
expect_any_instance_of(Rugged::Diff::Delta).to receive(:binary?).
and_return(true)
diff = described_class.new(@rugged_diff)
expect(diff.diff).not_to be_empty
end
end
end
context 'using a GitalyClient::Diff' do
let(:diff) do
described_class.new(
Gitlab::GitalyClient::Diff.new(
to_path: ".gitmodules",
from_path: ".gitmodules",
old_mode: 0100644,
new_mode: 0100644,
from_id: '357406f3075a57708d0163752905cc1576fceacc',
to_id: '8e5177d718c561d36efde08bad36b43687ee6bf0',
patch: raw_patch
)
)
end
context 'with a small diff' do
let(:raw_patch) { @raw_diff_hash[:diff] }
it 'initializes the diff' do
expect(diff.to_hash).to eq(@raw_diff_hash)
end
it 'does not prune the diff' do
expect(diff).not_to be_too_large
end
end
context 'using a diff that is too large' do
let(:raw_patch) { 'a' * 204800 }
it 'prunes the diff' do
expect(diff.diff).to be_empty
expect(diff).to be_too_large
end
end
end
end
describe 'straight diffs' do
let(:options) { { straight: true } }
let(:diffs) { described_class.between(repository, 'feature', 'master', options) }
it 'has the correct size' do
expect(diffs.size).to eq(24)
end
context 'diff' do
it 'is an instance of Diff' do
expect(diffs.first).to be_kind_of(described_class)
end
it 'has the correct new_path' do
expect(diffs.first.new_path).to eq('.DS_Store')
end
it 'has the correct diff' do
expect(diffs.first.diff).to include('Binary files /dev/null and b/.DS_Store differ')
end
end
end
describe '.between' do
let(:diffs) { described_class.between(repository, 'feature', 'master') }
subject { diffs }
it { is_expected.to be_kind_of Gitlab::Git::DiffCollection }
describe '#size' do
subject { super().size }
it { is_expected.to eq(1) }
end
context 'diff' do
subject { diffs.first }
it { is_expected.to be_kind_of described_class }
describe '#new_path' do
subject { super().new_path }
it { is_expected.to eq('files/ruby/feature.rb') }
end
describe '#diff' do
subject { super().diff }
it { is_expected.to include '+class Feature' }
end
end
end
describe '.filter_diff_options' do
let(:options) { { max_size: 100, invalid_opt: true } }
context "without default options" do
let(:filtered_options) { described_class.filter_diff_options(options) }
it "should filter invalid options" do
expect(filtered_options).not_to have_key(:invalid_opt)
end
end
context "with default options" do
let(:filtered_options) do
default_options = { max_size: 5, bad_opt: 1, ignore_whitespace: true }
described_class.filter_diff_options(options, default_options)
end
it "should filter invalid options" do
expect(filtered_options).not_to have_key(:invalid_opt)
expect(filtered_options).not_to have_key(:bad_opt)
end
it "should merge with default options" do
expect(filtered_options).to have_key(:ignore_whitespace)
end
it "should override default options" do
expect(filtered_options).to have_key(:max_size)
expect(filtered_options[:max_size]).to eq(100)
end
end
end
describe '#submodule?' do
before do
commit = repository.lookup('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
@diffs = commit.parents[0].diff(commit).patches
end
it { expect(described_class.new(@diffs[0]).submodule?).to eq(false) }
it { expect(described_class.new(@diffs[1]).submodule?).to eq(true) }
end
describe '#line_count' do
it 'returns the correct number of lines' do
diff = described_class.new(@rugged_diff)
expect(diff.line_count).to eq(9)
end
end
describe '#too_large?' do
it 'returns true for a diff that is too large' do
diff = described_class.new(diff: 'a' * 204800)
expect(diff.too_large?).to eq(true)
end
it 'returns false for a diff that is small enough' do
diff = described_class.new(diff: 'a')
expect(diff.too_large?).to eq(false)
end
it 'returns true for a diff that was explicitly marked as being too large' do
diff = described_class.new(diff: 'a')
diff.too_large!
expect(diff.too_large?).to eq(true)
end
end
describe '#collapsed?' do
it 'returns false by default even on quite big diff' do
diff = described_class.new(diff: 'a' * 20480)
expect(diff).not_to be_collapsed
end
it 'returns false by default for a diff that is small enough' do
diff = described_class.new(diff: 'a')
expect(diff).not_to be_collapsed
end
it 'returns true for a diff that was explicitly marked as being collapsed' do
diff = described_class.new(diff: 'a')
diff.collapse!
expect(diff).to be_collapsed
end
end
describe '#collapsed?' do
it 'returns true for a diff that is quite large' do
diff = described_class.new({ diff: 'a' * (described_class.collapse_limit + 1) }, expanded: false)
expect(diff).to be_collapsed
end
it 'returns false for a diff that is small enough' do
diff = described_class.new({ diff: 'a' }, expanded: false)
expect(diff).not_to be_collapsed
end
end
describe '#collapse!' do
it 'prunes the diff' do
diff = described_class.new(diff: "foo\nbar")
diff.collapse!
expect(diff.diff).to eq('')
expect(diff.line_count).to eq(0)
end
end
end