803 lines
19 KiB
Ruby
803 lines
19 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
RSpec.describe Gitlab::Git::DiffCollection do
|
|
before do
|
|
stub_const('MutatingConstantIterator', Class.new)
|
|
|
|
MutatingConstantIterator.class_eval do
|
|
include Enumerable
|
|
|
|
attr_reader :size
|
|
|
|
def initialize(count, value)
|
|
@count = count
|
|
@size = count
|
|
@value = value
|
|
end
|
|
|
|
def each
|
|
return enum_for(:each) unless block_given?
|
|
|
|
loop do
|
|
break if @count == 0
|
|
|
|
# It is critical to decrement before yielding. We may never reach the lines after 'yield'.
|
|
@count -= 1
|
|
yield @value
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
let(:overflow_max_bytes) { false }
|
|
let(:overflow_max_files) { false }
|
|
let(:overflow_max_lines) { false }
|
|
|
|
shared_examples 'overflow stuff' do
|
|
it 'returns the expected overflow values' do
|
|
subject.overflow?
|
|
expect(subject.overflow_max_bytes?).to eq(overflow_max_bytes)
|
|
expect(subject.overflow_max_files?).to eq(overflow_max_files)
|
|
expect(subject.overflow_max_lines?).to eq(overflow_max_lines)
|
|
end
|
|
end
|
|
|
|
subject do
|
|
Gitlab::Git::DiffCollection.new(
|
|
iterator,
|
|
max_files: max_files,
|
|
max_lines: max_lines,
|
|
limits: limits,
|
|
expanded: expanded
|
|
)
|
|
end
|
|
|
|
let(:iterator) { MutatingConstantIterator.new(file_count, fake_diff(line_length, line_count)) }
|
|
let(:file_count) { 0 }
|
|
let(:line_length) { 1 }
|
|
let(:line_count) { 1 }
|
|
let(:max_files) { 10 }
|
|
let(:max_lines) { 100 }
|
|
let(:limits) { true }
|
|
let(:expanded) { true }
|
|
|
|
describe '#to_a' do
|
|
subject { super().to_a }
|
|
|
|
it { is_expected.to be_kind_of ::Array }
|
|
end
|
|
|
|
describe '#decorate!' do
|
|
let(:file_count) { 3 }
|
|
|
|
it 'modifies the array in place' do
|
|
count = 0
|
|
subject.decorate! { |d| !d.nil? && count += 1 }
|
|
expect(subject.to_a).to eq([1, 2, 3])
|
|
expect(count).to eq(3)
|
|
end
|
|
|
|
it 'avoids future iterator iterations' do
|
|
subject.decorate! { |d| d unless d.nil? }
|
|
|
|
expect(iterator).not_to receive(:each)
|
|
|
|
subject.overflow?
|
|
end
|
|
end
|
|
|
|
context 'overflow handling' do
|
|
subject { super() }
|
|
|
|
let(:collapsed_safe_files) { false }
|
|
let(:collapsed_safe_lines) { false }
|
|
|
|
context 'adding few enough files' do
|
|
let(:file_count) { 3 }
|
|
|
|
context 'and few enough lines' do
|
|
let(:line_count) { 10 }
|
|
|
|
it_behaves_like 'overflow stuff'
|
|
|
|
describe '#overflow?' do
|
|
subject { super().overflow? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#empty?' do
|
|
subject { super().empty? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#real_size' do
|
|
subject { super().real_size }
|
|
|
|
it { is_expected.to eq('3') }
|
|
end
|
|
|
|
describe '#size' do
|
|
it { expect(subject.size).to eq(3) }
|
|
|
|
it 'does not change after peeking' do
|
|
subject.any?
|
|
expect(subject.size).to eq(3)
|
|
end
|
|
end
|
|
|
|
describe '#line_count' do
|
|
subject { super().line_count }
|
|
|
|
it { is_expected.to eq file_count * line_count }
|
|
end
|
|
|
|
context 'when limiting is disabled' do
|
|
let(:limits) { false }
|
|
let(:overflow_max_bytes) { false }
|
|
let(:overflow_max_files) { false }
|
|
let(:overflow_max_lines) { false }
|
|
|
|
it_behaves_like 'overflow stuff'
|
|
|
|
describe '#overflow?' do
|
|
subject { super().overflow? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#empty?' do
|
|
subject { super().empty? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#real_size' do
|
|
subject { super().real_size }
|
|
|
|
it { is_expected.to eq('3') }
|
|
end
|
|
|
|
describe '#size' do
|
|
it { expect(subject.size).to eq(3) }
|
|
|
|
it 'does not change after peeking' do
|
|
subject.any?
|
|
expect(subject.size).to eq(3)
|
|
end
|
|
end
|
|
|
|
describe '#line_count' do
|
|
subject { super().line_count }
|
|
|
|
it { is_expected.to eq file_count * line_count }
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'and too many lines' do
|
|
let(:line_count) { 1000 }
|
|
let(:overflow_max_lines) { true }
|
|
|
|
it_behaves_like 'overflow stuff'
|
|
|
|
describe '#overflow?' do
|
|
subject { super().overflow? }
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
|
|
describe '#empty?' do
|
|
subject { super().empty? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#real_size' do
|
|
subject { super().real_size }
|
|
|
|
it { is_expected.to eq('0+') }
|
|
end
|
|
|
|
describe '#line_count' do
|
|
subject { super().line_count }
|
|
|
|
it { is_expected.to eq 1000 }
|
|
end
|
|
|
|
it { expect(subject.size).to eq(0) }
|
|
|
|
context 'when limiting is disabled' do
|
|
let(:limits) { false }
|
|
let(:overflow_max_bytes) { false }
|
|
let(:overflow_max_files) { false }
|
|
let(:overflow_max_lines) { false }
|
|
|
|
it_behaves_like 'overflow stuff'
|
|
|
|
describe '#overflow?' do
|
|
subject { super().overflow? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#empty?' do
|
|
subject { super().empty? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#real_size' do
|
|
subject { super().real_size }
|
|
|
|
it { is_expected.to eq('3') }
|
|
end
|
|
|
|
describe '#line_count' do
|
|
subject { super().line_count }
|
|
|
|
it { is_expected.to eq file_count * line_count }
|
|
end
|
|
|
|
it { expect(subject.size).to eq(3) }
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'adding too many files' do
|
|
let(:file_count) { 11 }
|
|
let(:overflow_max_files) { true }
|
|
|
|
context 'and few enough lines' do
|
|
let(:line_count) { 1 }
|
|
|
|
it_behaves_like 'overflow stuff'
|
|
|
|
describe '#overflow?' do
|
|
subject { super().overflow? }
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
|
|
describe '#empty?' do
|
|
subject { super().empty? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#real_size' do
|
|
subject { super().real_size }
|
|
|
|
it { is_expected.to eq('10+') }
|
|
end
|
|
|
|
describe '#line_count' do
|
|
subject { super().line_count }
|
|
|
|
it { is_expected.to eq 10 }
|
|
end
|
|
|
|
it { expect(subject.size).to eq(10) }
|
|
|
|
context 'when limiting is disabled' do
|
|
let(:limits) { false }
|
|
let(:overflow_max_bytes) { false }
|
|
let(:overflow_max_files) { false }
|
|
let(:overflow_max_lines) { false }
|
|
|
|
it_behaves_like 'overflow stuff'
|
|
|
|
describe '#overflow?' do
|
|
subject { super().overflow? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#empty?' do
|
|
subject { super().empty? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#real_size' do
|
|
subject { super().real_size }
|
|
|
|
it { is_expected.to eq('11') }
|
|
end
|
|
|
|
describe '#line_count' do
|
|
subject { super().line_count }
|
|
|
|
it { is_expected.to eq file_count * line_count }
|
|
end
|
|
|
|
it { expect(subject.size).to eq(11) }
|
|
end
|
|
end
|
|
|
|
context 'and too many lines' do
|
|
let(:line_count) { 30 }
|
|
let(:overflow_max_lines) { true }
|
|
let(:overflow_max_files) { false }
|
|
|
|
it_behaves_like 'overflow stuff'
|
|
|
|
describe '#overflow?' do
|
|
subject { super().overflow? }
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
|
|
describe '#empty?' do
|
|
subject { super().empty? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#real_size' do
|
|
subject { super().real_size }
|
|
|
|
it { is_expected.to eq('3+') }
|
|
end
|
|
|
|
describe '#line_count' do
|
|
subject { super().line_count }
|
|
|
|
it { is_expected.to eq 120 }
|
|
end
|
|
|
|
it { expect(subject.size).to eq(3) }
|
|
|
|
context 'when limiting is disabled' do
|
|
let(:limits) { false }
|
|
let(:overflow_max_bytes) { false }
|
|
let(:overflow_max_files) { false }
|
|
let(:overflow_max_lines) { false }
|
|
|
|
it_behaves_like 'overflow stuff'
|
|
|
|
describe '#overflow?' do
|
|
subject { super().overflow? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#empty?' do
|
|
subject { super().empty? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#real_size' do
|
|
subject { super().real_size }
|
|
|
|
it { is_expected.to eq('11') }
|
|
end
|
|
|
|
describe '#line_count' do
|
|
subject { super().line_count }
|
|
|
|
it { is_expected.to eq file_count * line_count }
|
|
end
|
|
|
|
it { expect(subject.size).to eq(11) }
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'adding exactly the maximum number of files' do
|
|
let(:file_count) { 10 }
|
|
|
|
context 'and few enough lines' do
|
|
let(:line_count) { 1 }
|
|
|
|
it_behaves_like 'overflow stuff'
|
|
|
|
describe '#overflow?' do
|
|
subject { super().overflow? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#empty?' do
|
|
subject { super().empty? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#real_size' do
|
|
subject { super().real_size }
|
|
|
|
it { is_expected.to eq('10') }
|
|
end
|
|
|
|
describe '#line_count' do
|
|
subject { super().line_count }
|
|
|
|
it { is_expected.to eq file_count * line_count }
|
|
end
|
|
|
|
it { expect(subject.size).to eq(10) }
|
|
end
|
|
end
|
|
|
|
context 'adding too many bytes' do
|
|
let(:file_count) { 10 }
|
|
let(:line_length) { 5200 }
|
|
let(:overflow_max_bytes) { true }
|
|
|
|
it_behaves_like 'overflow stuff'
|
|
|
|
describe '#overflow?' do
|
|
subject { super().overflow? }
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
|
|
describe '#empty?' do
|
|
subject { super().empty? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#real_size' do
|
|
subject { super().real_size }
|
|
|
|
it { is_expected.to eq('9+') }
|
|
end
|
|
|
|
describe '#line_count' do
|
|
subject { super().line_count }
|
|
|
|
it { is_expected.to eq file_count * line_count }
|
|
end
|
|
|
|
it { expect(subject.size).to eq(9) }
|
|
|
|
context 'when limiting is disabled' do
|
|
let(:limits) { false }
|
|
let(:overflow_max_bytes) { false }
|
|
let(:overflow_max_files) { false }
|
|
let(:overflow_max_lines) { false }
|
|
|
|
it_behaves_like 'overflow stuff'
|
|
|
|
describe '#overflow?' do
|
|
subject { super().overflow? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#empty?' do
|
|
subject { super().empty? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#real_size' do
|
|
subject { super().real_size }
|
|
|
|
it { is_expected.to eq('10') }
|
|
end
|
|
|
|
describe '#line_count' do
|
|
subject { super().line_count }
|
|
|
|
it { is_expected.to eq file_count * line_count }
|
|
end
|
|
|
|
it { expect(subject.size).to eq(10) }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'empty collection' do
|
|
subject { Gitlab::Git::DiffCollection.new([]) }
|
|
|
|
it_behaves_like 'overflow stuff'
|
|
|
|
describe '#overflow?' do
|
|
subject { super().overflow? }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
describe '#empty?' do
|
|
subject { super().empty? }
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
|
|
describe '#size' do
|
|
subject { super().size }
|
|
|
|
it { is_expected.to eq(0) }
|
|
end
|
|
|
|
describe '#real_size' do
|
|
subject { super().real_size }
|
|
|
|
it { is_expected.to eq('0') }
|
|
end
|
|
|
|
describe '#line_count' do
|
|
subject { super().line_count }
|
|
|
|
it { is_expected.to eq 0 }
|
|
end
|
|
end
|
|
|
|
describe '#each' do
|
|
context 'when diff are too large' do
|
|
let(:collection) do
|
|
Gitlab::Git::DiffCollection.new([{ diff: 'a' * 204800 }])
|
|
end
|
|
|
|
it 'yields Diff instances even when they are too large' do
|
|
expect { |b| collection.each(&b) }
|
|
.to yield_with_args(an_instance_of(Gitlab::Git::Diff))
|
|
end
|
|
|
|
it 'prunes diffs that are too large' do
|
|
diff = nil
|
|
|
|
collection.each do |d|
|
|
diff = d
|
|
end
|
|
|
|
expect(diff.diff).to eq('')
|
|
end
|
|
end
|
|
|
|
context 'when diff is quite large will collapse by default' do
|
|
let(:iterator) { [{ diff: 'a' * 20480 }] }
|
|
|
|
context 'when no collapse is set' do
|
|
let(:expanded) { true }
|
|
|
|
it 'yields Diff instances even when they are quite big' do
|
|
expect { |b| subject.each(&b) }
|
|
.to yield_with_args(an_instance_of(Gitlab::Git::Diff))
|
|
end
|
|
|
|
it 'does not prune diffs' do
|
|
diff = nil
|
|
|
|
subject.each do |d|
|
|
diff = d
|
|
end
|
|
|
|
expect(diff.diff).not_to eq('')
|
|
end
|
|
end
|
|
|
|
context 'when no collapse is unset' do
|
|
let(:expanded) { false }
|
|
|
|
it 'yields Diff instances even when they are quite big' do
|
|
expect { |b| subject.each(&b) }
|
|
.to yield_with_args(an_instance_of(Gitlab::Git::Diff))
|
|
end
|
|
|
|
context 'single-file collections' do
|
|
it 'does not prune diffs' do
|
|
diff = nil
|
|
|
|
subject.each do |d|
|
|
diff = d
|
|
end
|
|
|
|
expect(diff.diff).not_to eq('')
|
|
end
|
|
end
|
|
|
|
context 'multi-file collections' do
|
|
let(:iterator) { [{ diff: 'b' }, { diff: 'a' * 20480 }] }
|
|
|
|
it 'prunes diffs that are quite big' do
|
|
diff = nil
|
|
|
|
subject.each do |d|
|
|
diff = d
|
|
end
|
|
|
|
expect(diff.diff).to eq('')
|
|
end
|
|
end
|
|
|
|
context 'when go over safe limits on files' do
|
|
let(:iterator) { [fake_diff(1, 1)] * 4 }
|
|
|
|
before do
|
|
allow(Gitlab::Git::DiffCollection)
|
|
.to receive(:default_limits)
|
|
.and_return({ max_files: 2, max_lines: max_lines })
|
|
end
|
|
|
|
it 'prunes diffs by default even little ones and sets collapsed_safe_files true' do
|
|
subject.each_with_index do |d, i|
|
|
if i < 2
|
|
expect(d.diff).not_to eq('')
|
|
else # 90 lines
|
|
expect(d.diff).to eq('')
|
|
end
|
|
end
|
|
|
|
expect(subject.collapsed_safe_files?).to eq(true)
|
|
end
|
|
end
|
|
|
|
context 'when go over safe limits on lines' do
|
|
let(:iterator) do
|
|
[
|
|
fake_diff(1, 45),
|
|
fake_diff(1, 45),
|
|
fake_diff(1, 20480),
|
|
fake_diff(1, 1)
|
|
]
|
|
end
|
|
|
|
before do
|
|
allow(Gitlab::Git::DiffCollection)
|
|
.to receive(:default_limits)
|
|
.and_return({ max_files: max_files, max_lines: 80 })
|
|
end
|
|
|
|
it 'prunes diffs by default even little ones and sets collapsed_safe_lines true' do
|
|
subject.each_with_index do |d, i|
|
|
if i < 2
|
|
expect(d.diff).not_to eq('')
|
|
else # 90 lines
|
|
expect(d.diff).to eq('')
|
|
end
|
|
end
|
|
|
|
expect(subject.collapsed_safe_lines?).to eq(true)
|
|
end
|
|
end
|
|
|
|
context 'when go over safe limits on bytes' do
|
|
let(:iterator) do
|
|
[
|
|
fake_diff(5, 10),
|
|
fake_diff(5000, 10),
|
|
fake_diff(5, 10),
|
|
fake_diff(5, 10)
|
|
]
|
|
end
|
|
|
|
before do
|
|
allow(Gitlab::CurrentSettings).to receive(:diff_max_patch_bytes).and_return(1.megabyte)
|
|
|
|
allow(Gitlab::Git::DiffCollection)
|
|
.to receive(:default_limits)
|
|
.and_return({ max_files: 4, max_lines: 3000 })
|
|
end
|
|
|
|
it 'prunes diffs by default even little ones and sets collapsed_safe_bytes true' do
|
|
subject.each_with_index do |d, i|
|
|
if i < 2
|
|
expect(d.diff).not_to eq('')
|
|
else # > 80 bytes
|
|
expect(d.diff).to eq('')
|
|
end
|
|
end
|
|
|
|
expect(subject.collapsed_safe_bytes?).to eq(true)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when limiting is disabled' do
|
|
let(:limits) { false }
|
|
|
|
it 'yields Diff instances even when they are quite big' do
|
|
expect { |b| subject.each(&b) }
|
|
.to yield_with_args(an_instance_of(Gitlab::Git::Diff))
|
|
end
|
|
|
|
it 'does not prune diffs' do
|
|
diff = nil
|
|
|
|
subject.each do |d|
|
|
diff = d
|
|
end
|
|
|
|
expect(diff.diff).not_to eq('')
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when offset_index is given' do
|
|
subject do
|
|
Gitlab::Git::DiffCollection.new(
|
|
iterator,
|
|
max_files: max_files,
|
|
max_lines: max_lines,
|
|
limits: limits,
|
|
offset_index: 2,
|
|
expanded: expanded
|
|
)
|
|
end
|
|
|
|
def diff(raw)
|
|
raw['diff']
|
|
end
|
|
|
|
let(:iterator) do
|
|
[
|
|
fake_diff(1, 1),
|
|
fake_diff(2, 2),
|
|
fake_diff(3, 3),
|
|
fake_diff(4, 4)
|
|
]
|
|
end
|
|
|
|
it 'does not yield diffs before the offset' do
|
|
expect(subject.to_a.map(&:diff)).to eq(
|
|
[
|
|
diff(fake_diff(3, 3)),
|
|
diff(fake_diff(4, 4))
|
|
]
|
|
)
|
|
end
|
|
|
|
context 'when go over safe limits on bytes' do
|
|
let(:iterator) do
|
|
[
|
|
fake_diff(1, 10), # 10
|
|
fake_diff(1, 10), # 20
|
|
fake_diff(1, 15), # 35
|
|
fake_diff(1, 20), # 55
|
|
fake_diff(1, 45), # 100 - limit hit
|
|
fake_diff(1, 45),
|
|
fake_diff(1, 20480),
|
|
fake_diff(1, 1)
|
|
]
|
|
end
|
|
|
|
before do
|
|
allow(Gitlab::Git::DiffCollection)
|
|
.to receive(:default_limits)
|
|
.and_return({ max_files: max_files, max_lines: 80 })
|
|
end
|
|
|
|
it 'considers size of diffs before the offset for prunning' do
|
|
expect(subject.to_a.map(&:diff)).to eq(
|
|
[
|
|
diff(fake_diff(1, 15)),
|
|
diff(fake_diff(1, 20))
|
|
]
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '.limits' do
|
|
let(:options) { {} }
|
|
|
|
subject { described_class.limits(options) }
|
|
|
|
context 'when options do not include max_patch_bytes_for_file_extension' do
|
|
it 'sets max_patch_bytes_for_file_extension as empty' do
|
|
expect(subject[:max_patch_bytes_for_file_extension]).to eq({})
|
|
end
|
|
end
|
|
|
|
context 'when options include max_patch_bytes_for_file_extension' do
|
|
let(:options) { { max_patch_bytes_for_file_extension: { '.file' => 1 } } }
|
|
|
|
it 'sets value for max_patch_bytes_for_file_extension' do
|
|
expect(subject[:max_patch_bytes_for_file_extension]).to eq({ '.file' => 1 })
|
|
end
|
|
end
|
|
end
|
|
|
|
def fake_diff(line_length, line_count)
|
|
{ 'diff' => "#{'a' * line_length}\n" * line_count }
|
|
end
|
|
end
|