diff options
Diffstat (limited to 'spec/lib/gitlab/ci')
-rw-r--r-- | spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb | 11 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/build/credentials/factory_spec.rb | 4 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/build/credentials/registry_spec.rb | 6 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/config/entry/global_spec.rb | 6 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/config/entry/variables_spec.rb | 8 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/cron_parser_spec.rb | 186 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/status/build/action_spec.rb | 56 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/status/build/factory_spec.rb | 51 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/status/build/play_spec.rb | 45 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/status/extended_spec.rb | 6 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/status/group/common_spec.rb | 20 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/status/group/factory_spec.rb | 13 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/trace/stream_spec.rb | 256 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/trace_reader_spec.rb | 52 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/trace_spec.rb | 228 |
15 files changed, 847 insertions, 101 deletions
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb index abc93e1b44a..3b905611467 100644 --- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb +++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb @@ -135,6 +135,17 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do subject { |example| path(example).nodes } it { is_expected.to eq 4 } end + + describe '#blob' do + let(:file_entry) { |example| path(example) } + subject { file_entry.blob } + + it 'returns a blob representing the entry data' do + expect(subject).to be_a(Blob) + expect(subject.path).to eq(file_entry.path) + expect(subject.size).to eq(file_entry.metadata[:size]) + end + end end describe 'non-existent/', path: 'non-existent/' do diff --git a/spec/lib/gitlab/ci/build/credentials/factory_spec.rb b/spec/lib/gitlab/ci/build/credentials/factory_spec.rb index 10b4b7a8826..d53db05e5e6 100644 --- a/spec/lib/gitlab/ci/build/credentials/factory_spec.rb +++ b/spec/lib/gitlab/ci/build/credentials/factory_spec.rb @@ -3,14 +3,14 @@ require 'spec_helper' describe Gitlab::Ci::Build::Credentials::Factory do let(:build) { create(:ci_build, name: 'spinach', stage: 'test', stage_idx: 0) } - subject { Gitlab::Ci::Build::Credentials::Factory.new(build).create! } + subject { described_class.new(build).create! } class TestProvider def initialize(build); end end before do - allow_any_instance_of(Gitlab::Ci::Build::Credentials::Factory).to receive(:providers).and_return([TestProvider]) + allow_any_instance_of(described_class).to receive(:providers).and_return([TestProvider]) end context 'when provider is valid' do diff --git a/spec/lib/gitlab/ci/build/credentials/registry_spec.rb b/spec/lib/gitlab/ci/build/credentials/registry_spec.rb index 84e44dd53e2..c6054138cde 100644 --- a/spec/lib/gitlab/ci/build/credentials/registry_spec.rb +++ b/spec/lib/gitlab/ci/build/credentials/registry_spec.rb @@ -4,14 +4,14 @@ describe Gitlab::Ci::Build::Credentials::Registry do let(:build) { create(:ci_build, name: 'spinach', stage: 'test', stage_idx: 0) } let(:registry_url) { 'registry.example.com:5005' } - subject { Gitlab::Ci::Build::Credentials::Registry.new(build) } + subject { described_class.new(build) } before do stub_container_registry_config(host_port: registry_url) end it 'contains valid DockerRegistry credentials' do - expect(subject).to be_kind_of(Gitlab::Ci::Build::Credentials::Registry) + expect(subject).to be_kind_of(described_class) expect(subject.username).to eq 'gitlab-ci-token' expect(subject.password).to eq build.token @@ -20,7 +20,7 @@ describe Gitlab::Ci::Build::Credentials::Registry do end describe '.valid?' do - subject { Gitlab::Ci::Build::Credentials::Registry.new(build).valid? } + subject { described_class.new(build).valid? } context 'when registry is enabled' do before do diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb index 684d01e9056..23270ad5053 100644 --- a/spec/lib/gitlab/ci/config/entry/global_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb @@ -113,7 +113,7 @@ describe Gitlab::Ci::Config::Entry::Global do describe '#variables_value' do it 'returns variables' do - expect(global.variables_value).to eq(VAR: 'value') + expect(global.variables_value).to eq('VAR' => 'value') end end @@ -154,7 +154,7 @@ describe Gitlab::Ci::Config::Entry::Global do services: ['postgres:9.1', 'mysql:5.5'], stage: 'test', cache: { key: 'k', untracked: true, paths: ['public/'] }, - variables: { VAR: 'value' }, + variables: { 'VAR' => 'value' }, ignore: false, after_script: ['make clean'] }, spinach: { name: :spinach, @@ -167,7 +167,7 @@ describe Gitlab::Ci::Config::Entry::Global do cache: { key: 'k', untracked: true, paths: ['public/'] }, variables: {}, ignore: false, - after_script: ['make clean'] }, + after_script: ['make clean'] } ) end end diff --git a/spec/lib/gitlab/ci/config/entry/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/variables_spec.rb index f15f02f403e..84bfef9e8ad 100644 --- a/spec/lib/gitlab/ci/config/entry/variables_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/variables_spec.rb @@ -13,6 +13,14 @@ describe Gitlab::Ci::Config::Entry::Variables do it 'returns hash with key value strings' do expect(entry.value).to eq config end + + context 'with numeric keys and values in the config' do + let(:config) { { 10 => 20 } } + + it 'converts numeric key and numeric value into strings' do + expect(entry.value).to eq('10' => '20') + end + end end describe '#errors' do diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb new file mode 100644 index 00000000000..809fda11879 --- /dev/null +++ b/spec/lib/gitlab/ci/cron_parser_spec.rb @@ -0,0 +1,186 @@ +require 'spec_helper' + +describe Gitlab::Ci::CronParser do + shared_examples_for "returns time in the future" do + it { is_expected.to be > Time.now } + end + + describe '#next_time_from' do + subject { described_class.new(cron, cron_timezone).next_time_from(Time.now) } + + context 'when cron and cron_timezone are valid' do + context 'when specific time' do + let(:cron) { '3 4 5 6 *' } + let(:cron_timezone) { 'UTC' } + + it_behaves_like "returns time in the future" + + it 'returns exact time' do + expect(subject.min).to eq(3) + expect(subject.hour).to eq(4) + expect(subject.day).to eq(5) + expect(subject.month).to eq(6) + end + end + + context 'when specific day of week' do + let(:cron) { '* * * * 0' } + let(:cron_timezone) { 'UTC' } + + it_behaves_like "returns time in the future" + + it 'returns exact day of week' do + expect(subject.wday).to eq(0) + end + end + + context 'when slash used' do + let(:cron) { '*/10 */6 */10 */10 *' } + let(:cron_timezone) { 'UTC' } + + it_behaves_like "returns time in the future" + + it 'returns specific time' do + expect(subject.min).to be_in([0, 10, 20, 30, 40, 50]) + expect(subject.hour).to be_in([0, 6, 12, 18]) + expect(subject.day).to be_in([1, 11, 21, 31]) + expect(subject.month).to be_in([1, 11]) + end + end + + context 'when range used' do + let(:cron) { '0,20,40 * 1-5 * *' } + let(:cron_timezone) { 'UTC' } + + it_behaves_like "returns time in the future" + + it 'returns specific time' do + expect(subject.min).to be_in([0, 20, 40]) + expect(subject.day).to be_in((1..5).to_a) + end + end + + context 'when cron_timezone is TZInfo format' do + before do + allow(Time).to receive(:zone) + .and_return(ActiveSupport::TimeZone['UTC']) + end + + let(:hour_in_utc) do + ActiveSupport::TimeZone[cron_timezone] + .now.change(hour: 0).in_time_zone('UTC').hour + end + + context 'when cron_timezone is US/Pacific' do + let(:cron) { '* 0 * * *' } + let(:cron_timezone) { 'US/Pacific' } + + it_behaves_like "returns time in the future" + + it 'converts time in server time zone' do + expect(subject.hour).to eq(hour_in_utc) + end + end + end + + context 'when cron_timezone is ActiveSupport::TimeZone format' do + before do + allow(Time).to receive(:zone) + .and_return(ActiveSupport::TimeZone['UTC']) + end + + let(:hour_in_utc) do + ActiveSupport::TimeZone[cron_timezone] + .now.change(hour: 0).in_time_zone('UTC').hour + end + + context 'when cron_timezone is Berlin' do + let(:cron) { '* 0 * * *' } + let(:cron_timezone) { 'Berlin' } + + it_behaves_like "returns time in the future" + + it 'converts time in server time zone' do + expect(subject.hour).to eq(hour_in_utc) + end + end + + context 'when cron_timezone is Eastern Time (US & Canada)' do + let(:cron) { '* 0 * * *' } + let(:cron_timezone) { 'Eastern Time (US & Canada)' } + + it_behaves_like "returns time in the future" + + it 'converts time in server time zone' do + expect(subject.hour).to eq(hour_in_utc) + end + end + end + end + + context 'when cron and cron_timezone are invalid' do + let(:cron) { 'invalid_cron' } + let(:cron_timezone) { 'invalid_cron_timezone' } + + it { is_expected.to be_nil } + end + + context 'when cron syntax is quoted' do + let(:cron) { "'0 * * * *'" } + let(:cron_timezone) { 'UTC' } + + it { expect(subject).to be_nil } + end + + context 'when cron syntax is rufus-scheduler syntax' do + let(:cron) { 'every 3h' } + let(:cron_timezone) { 'UTC' } + + it { expect(subject).to be_nil } + end + end + + describe '#cron_valid?' do + subject { described_class.new(cron, Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE).cron_valid? } + + context 'when cron is valid' do + let(:cron) { '* * * * *' } + + it { is_expected.to eq(true) } + end + + context 'when cron is invalid' do + let(:cron) { '*********' } + + it { is_expected.to eq(false) } + end + + context 'when cron syntax is quoted' do + let(:cron) { "'0 * * * *'" } + + it { is_expected.to eq(false) } + end + end + + describe '#cron_timezone_valid?' do + subject { described_class.new(Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_CRON, cron_timezone).cron_timezone_valid? } + + context 'when cron is valid' do + let(:cron_timezone) { 'Europe/Istanbul' } + + it { is_expected.to eq(true) } + end + + context 'when cron is invalid' do + let(:cron_timezone) { 'Invalid-zone' } + + it { is_expected.to eq(false) } + end + + context 'when cron_timezone is ActiveSupport::TimeZone format' do + let(:cron_timezone) { 'Eastern Time (US & Canada)' } + + it { is_expected.to eq(true) } + end + end +end diff --git a/spec/lib/gitlab/ci/status/build/action_spec.rb b/spec/lib/gitlab/ci/status/build/action_spec.rb new file mode 100644 index 00000000000..8c25f72804b --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/action_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Action do + let(:status) { double('core status') } + let(:user) { double('user') } + + subject do + described_class.new(status) + end + + describe '#label' do + before do + allow(status).to receive(:label).and_return('label') + end + + context 'when status has action' do + before do + allow(status).to receive(:has_action?).and_return(true) + end + + it 'does not append text' do + expect(subject.label).to eq 'label' + end + end + + context 'when status does not have action' do + before do + allow(status).to receive(:has_action?).and_return(false) + end + + it 'appends text about action not allowed' do + expect(subject.label).to eq 'label (not allowed)' + end + end + end + + describe '.matches?' do + subject { described_class.matches?(build, user) } + + context 'when build is an action' do + let(:build) { create(:ci_build, :manual) } + + it 'is a correct match' do + expect(subject).to be true + end + end + + context 'when build is not manual' do + let(:build) { create(:ci_build) } + + it 'does not match' do + expect(subject).to be false + end + end + end +end diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index e648a3ac3a2..185bb9098da 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -204,11 +204,12 @@ describe Gitlab::Ci::Status::Build::Factory do it 'matches correct extended statuses' do expect(factory.extended_statuses) - .to eq [Gitlab::Ci::Status::Build::Play] + .to eq [Gitlab::Ci::Status::Build::Play, + Gitlab::Ci::Status::Build::Action] end - it 'fabricates a play detailed status' do - expect(status).to be_a Gitlab::Ci::Status::Build::Play + it 'fabricates action detailed status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Action end it 'fabricates status with correct details' do @@ -216,11 +217,26 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.group).to eq 'manual' expect(status.icon).to eq 'icon_status_manual' expect(status.favicon).to eq 'favicon_status_manual' - expect(status.label).to eq 'manual play action' + expect(status.label).to include 'manual play action' expect(status).to have_details - expect(status).to have_action expect(status.action_path).to include 'play' end + + context 'when user has ability to play action' do + before do + build.project.add_master(user) + end + + it 'fabricates status that has action' do + expect(status).to have_action + end + end + + context 'when user does not have ability to play action' do + it 'fabricates status that has no action' do + expect(status).not_to have_action + end + end end context 'when build is an environment stop action' do @@ -232,21 +248,24 @@ describe Gitlab::Ci::Status::Build::Factory do it 'matches correct extended statuses' do expect(factory.extended_statuses) - .to eq [Gitlab::Ci::Status::Build::Stop] + .to eq [Gitlab::Ci::Status::Build::Stop, + Gitlab::Ci::Status::Build::Action] end - it 'fabricates a stop detailed status' do - expect(status).to be_a Gitlab::Ci::Status::Build::Stop + it 'fabricates action detailed status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Action end - it 'fabricates status with correct details' do - expect(status.text).to eq 'manual' - expect(status.group).to eq 'manual' - expect(status.icon).to eq 'icon_status_manual' - expect(status.favicon).to eq 'favicon_status_manual' - expect(status.label).to eq 'manual stop action' - expect(status).to have_details - expect(status).to have_action + context 'when user is not allowed to execute manual action' do + it 'fabricates status with correct details' do + expect(status.text).to eq 'manual' + expect(status.group).to eq 'manual' + expect(status.icon).to eq 'icon_status_manual' + expect(status.favicon).to eq 'favicon_status_manual' + expect(status.label).to eq 'manual stop action (not allowed)' + expect(status).to have_details + expect(status).not_to have_action + end end end end diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb index 6c97a4fe5ca..f5d0f977768 100644 --- a/spec/lib/gitlab/ci/status/build/play_spec.rb +++ b/spec/lib/gitlab/ci/status/build/play_spec.rb @@ -1,43 +1,48 @@ require 'spec_helper' describe Gitlab::Ci::Status::Build::Play do - let(:status) { double('core') } - let(:user) { double('user') } + let(:user) { create(:user) } + let(:build) { create(:ci_build, :manual) } + let(:status) { Gitlab::Ci::Status::Core.new(build, user) } subject { described_class.new(status) } describe '#label' do - it { expect(subject.label).to eq 'manual play action' } + it 'has a label that says it is a manual action' do + expect(subject.label).to eq 'manual play action' + end end - describe 'action details' do - let(:user) { create(:user) } - let(:build) { create(:ci_build) } - let(:status) { Gitlab::Ci::Status::Core.new(build, user) } - - describe '#has_action?' do - context 'when user is allowed to update build' do - before { build.project.team << [user, :developer] } + describe '#has_action?' do + context 'when user is allowed to update build' do + context 'when user can push to branch' do + before { build.project.add_master(user) } it { is_expected.to have_action } end - context 'when user is not allowed to update build' do + context 'when user can not push to the branch' do + before { build.project.add_developer(user) } + it { is_expected.not_to have_action } end end - describe '#action_path' do - it { expect(subject.action_path).to include "#{build.id}/play" } + context 'when user is not allowed to update build' do + it { is_expected.not_to have_action } end + end - describe '#action_icon' do - it { expect(subject.action_icon).to eq 'icon_action_play' } - end + describe '#action_path' do + it { expect(subject.action_path).to include "#{build.id}/play" } + end - describe '#action_title' do - it { expect(subject.action_title).to eq 'Play' } - end + describe '#action_icon' do + it { expect(subject.action_icon).to eq 'icon_action_play' } + end + + describe '#action_title' do + it { expect(subject.action_title).to eq 'Play' } end describe '.matches?' do diff --git a/spec/lib/gitlab/ci/status/extended_spec.rb b/spec/lib/gitlab/ci/status/extended_spec.rb index c2d74ca5cde..6eacb07078b 100644 --- a/spec/lib/gitlab/ci/status/extended_spec.rb +++ b/spec/lib/gitlab/ci/status/extended_spec.rb @@ -1,12 +1,8 @@ require 'spec_helper' describe Gitlab::Ci::Status::Extended do - subject do - Class.new.include(described_class) - end - it 'requires subclass to implement matcher' do - expect { subject.matches?(double, double) } + expect { described_class.matches?(double, double) } .to raise_error(NotImplementedError) end end diff --git a/spec/lib/gitlab/ci/status/group/common_spec.rb b/spec/lib/gitlab/ci/status/group/common_spec.rb new file mode 100644 index 00000000000..c0ca05881f5 --- /dev/null +++ b/spec/lib/gitlab/ci/status/group/common_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Group::Common do + subject do + Gitlab::Ci::Status::Core.new(double, double) + .extend(described_class) + end + + it 'does not have action' do + expect(subject).not_to have_action + end + + it 'has details' do + expect(subject).not_to have_details + end + + it 'has no details_path' do + expect(subject.details_path).to be_falsy + end +end diff --git a/spec/lib/gitlab/ci/status/group/factory_spec.rb b/spec/lib/gitlab/ci/status/group/factory_spec.rb new file mode 100644 index 00000000000..0cd83123938 --- /dev/null +++ b/spec/lib/gitlab/ci/status/group/factory_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Group::Factory do + it 'inherits from the core factory' do + expect(described_class) + .to be < Gitlab::Ci::Status::Factory + end + + it 'exposes group helpers' do + expect(described_class.common_helpers) + .to eq Gitlab::Ci::Status::Group::Common + end +end diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb new file mode 100644 index 00000000000..40ac5a3ed37 --- /dev/null +++ b/spec/lib/gitlab/ci/trace/stream_spec.rb @@ -0,0 +1,256 @@ +require 'spec_helper' + +describe Gitlab::Ci::Trace::Stream do + describe 'delegates' do + subject { described_class.new { nil } } + + it { is_expected.to delegate_method(:close).to(:stream) } + it { is_expected.to delegate_method(:tell).to(:stream) } + it { is_expected.to delegate_method(:seek).to(:stream) } + it { is_expected.to delegate_method(:size).to(:stream) } + it { is_expected.to delegate_method(:path).to(:stream) } + it { is_expected.to delegate_method(:truncate).to(:stream) } + it { is_expected.to delegate_method(:valid?).to(:stream).as(:present?) } + it { is_expected.to delegate_method(:file?).to(:path).as(:present?) } + end + + describe '#limit' do + let(:stream) do + described_class.new do + StringIO.new((1..8).to_a.join("\n")) + end + end + + it 'if size is larger we start from beginning' do + stream.limit(20) + + expect(stream.tell).to eq(0) + end + + it 'if size is smaller we start from the end' do + stream.limit(2) + + expect(stream.raw).to eq("8") + end + + context 'when the trace contains ANSI sequence and Unicode' do + let(:stream) do + described_class.new do + File.open(expand_fixture_path('trace/ansi-sequence-and-unicode')) + end + end + + it 'forwards to the next linefeed, case 1' do + stream.limit(7) + + result = stream.raw + + expect(result).to eq('') + expect(result.encoding).to eq(Encoding.default_external) + end + + it 'forwards to the next linefeed, case 2' do + stream.limit(29) + + result = stream.raw + + expect(result).to eq("\e[01;32m許功蓋\e[0m\n") + expect(result.encoding).to eq(Encoding.default_external) + end + + # See https://gitlab.com/gitlab-org/gitlab-ce/issues/30796 + it 'reads in binary, output as Encoding.default_external' do + stream.limit(52) + + result = stream.html + + expect(result).to eq("ヾ(´༎ຶД༎ຶ`)ノ<br><span class=\"term-fg-green\">許功蓋</span><br>") + expect(result.encoding).to eq(Encoding.default_external) + end + end + end + + describe '#append' do + let(:tempfile) { Tempfile.new } + + let(:stream) do + described_class.new do + tempfile.write("12345678") + tempfile.rewind + tempfile + end + end + + after do + tempfile.unlink + end + + it "truncates and append content" do + stream.append("89", 4) + stream.seek(0) + + expect(stream.size).to eq(6) + expect(stream.raw).to eq("123489") + end + + it 'appends in binary mode' do + '😺'.force_encoding('ASCII-8BIT').each_char.with_index do |byte, offset| + stream.append(byte, offset) + end + + stream.seek(0) + + expect(stream.size).to eq(4) + expect(stream.raw).to eq('😺') + end + end + + describe '#set' do + let(:stream) do + described_class.new do + StringIO.new("12345678") + end + end + + before do + stream.set("8901") + end + + it "overwrite content" do + stream.seek(0) + + expect(stream.size).to eq(4) + expect(stream.raw).to eq("8901") + end + end + + describe '#raw' do + let(:path) { __FILE__ } + let(:lines) { File.readlines(path) } + let(:stream) do + described_class.new do + File.open(path) + end + end + + it 'returns all contents if last_lines is not specified' do + result = stream.raw + + expect(result).to eq(lines.join) + expect(result.encoding).to eq(Encoding.default_external) + end + + context 'limit max lines' do + before do + # specifying BUFFER_SIZE forces to seek backwards + allow(described_class).to receive(:BUFFER_SIZE) + .and_return(2) + end + + it 'returns last few lines' do + result = stream.raw(last_lines: 2) + + expect(result).to eq(lines.last(2).join) + expect(result.encoding).to eq(Encoding.default_external) + end + + it 'returns everything if trying to get too many lines' do + result = stream.raw(last_lines: lines.size * 2) + + expect(result).to eq(lines.join) + expect(result.encoding).to eq(Encoding.default_external) + end + end + end + + describe '#html_with_state' do + let(:stream) do + described_class.new do + StringIO.new("1234") + end + end + + it 'returns html content with state' do + result = stream.html_with_state + + expect(result.html).to eq("1234") + end + + context 'follow-up state' do + let!(:last_result) { stream.html_with_state } + + before do + stream.append("5678", 4) + stream.seek(0) + end + + it "returns appended trace" do + result = stream.html_with_state(last_result.state) + + expect(result.append).to be_truthy + expect(result.html).to eq("5678") + end + end + end + + describe '#html' do + let(:stream) do + described_class.new do + StringIO.new("12\n34\n56") + end + end + + it "returns html" do + expect(stream.html).to eq("12<br>34<br>56") + end + + it "returns html for last line only" do + expect(stream.html(last_lines: 1)).to eq("56") + end + end + + describe '#extract_coverage' do + let(:stream) do + described_class.new do + StringIO.new(data) + end + end + + subject { stream.extract_coverage(regex) } + + context 'valid content & regex' do + let(:data) { 'Coverage 1033 / 1051 LOC (98.29%) covered' } + let(:regex) { '\(\d+.\d+\%\) covered' } + + it { is_expected.to eq("98.29") } + end + + context 'valid content & bad regex' do + let(:data) { 'Coverage 1033 / 1051 LOC (98.29%) covered\n' } + let(:regex) { 'very covered' } + + it { is_expected.to be_nil } + end + + context 'no coverage content & regex' do + let(:data) { 'No coverage for today :sad:' } + let(:regex) { '\(\d+.\d+\%\) covered' } + + it { is_expected.to be_nil } + end + + context 'multiple results in content & regex' do + let(:data) { ' (98.39%) covered. (98.29%) covered' } + let(:regex) { '\(\d+.\d+\%\) covered' } + + it { is_expected.to eq("98.29") } + end + + context 'using a regex capture' do + let(:data) { 'TOTAL 9926 3489 65%' } + let(:regex) { 'TOTAL\s+\d+\s+\d+\s+(\d{1,3}\%)' } + + it { is_expected.to eq("65") } + end + end +end diff --git a/spec/lib/gitlab/ci/trace_reader_spec.rb b/spec/lib/gitlab/ci/trace_reader_spec.rb deleted file mode 100644 index ff5551bf703..00000000000 --- a/spec/lib/gitlab/ci/trace_reader_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Ci::TraceReader do - let(:path) { __FILE__ } - let(:lines) { File.readlines(path) } - let(:bytesize) { lines.sum(&:bytesize) } - - it 'returns last few lines' do - 10.times do - subject = build_subject - last_lines = random_lines - - expected = lines.last(last_lines).join - result = subject.read(last_lines: last_lines) - - expect(result).to eq(expected) - expect(result.encoding).to eq(Encoding.default_external) - end - end - - it 'returns everything if trying to get too many lines' do - result = build_subject.read(last_lines: lines.size * 2) - - expect(result).to eq(lines.join) - expect(result.encoding).to eq(Encoding.default_external) - end - - it 'returns all contents if last_lines is not specified' do - result = build_subject.read - - expect(result).to eq(lines.join) - expect(result.encoding).to eq(Encoding.default_external) - end - - it 'raises an error if not passing an integer for last_lines' do - expect do - build_subject.read(last_lines: lines) - end.to raise_error(ArgumentError) - end - - def random_lines - Random.rand(lines.size) + 1 - end - - def random_buffer - Random.rand(bytesize) + 1 - end - - def build_subject - described_class.new(__FILE__, buffer_size: random_buffer) - end -end diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb new file mode 100644 index 00000000000..9cb0b62590a --- /dev/null +++ b/spec/lib/gitlab/ci/trace_spec.rb @@ -0,0 +1,228 @@ +require 'spec_helper' + +describe Gitlab::Ci::Trace do + let(:build) { create(:ci_build) } + let(:trace) { described_class.new(build) } + + describe "associations" do + it { expect(trace).to respond_to(:job) } + it { expect(trace).to delegate_method(:old_trace).to(:job) } + end + + describe '#html' do + before do + trace.set("12\n34") + end + + it "returns formatted html" do + expect(trace.html).to eq("12<br>34") + end + + it "returns last line of formatted html" do + expect(trace.html(last_lines: 1)).to eq("34") + end + end + + describe '#raw' do + before do + trace.set("12\n34") + end + + it "returns raw output" do + expect(trace.raw).to eq("12\n34") + end + + it "returns last line of raw output" do + expect(trace.raw(last_lines: 1)).to eq("34") + end + end + + describe '#extract_coverage' do + let(:regex) { '\(\d+.\d+\%\) covered' } + + context 'matching coverage' do + before do + trace.set('Coverage 1033 / 1051 LOC (98.29%) covered') + end + + it "returns valid coverage" do + expect(trace.extract_coverage(regex)).to eq("98.29") + end + end + + context 'no coverage' do + before do + trace.set('No coverage') + end + + it 'returs nil' do + expect(trace.extract_coverage(regex)).to be_nil + end + end + end + + describe '#set' do + before do + trace.set("12") + end + + it "returns trace" do + expect(trace.raw).to eq("12") + end + + context 'overwrite trace' do + before do + trace.set("34") + end + + it "returns new trace" do + expect(trace.raw).to eq("34") + end + end + + context 'runners token' do + let(:token) { 'my_secret_token' } + + before do + build.project.update(runners_token: token) + trace.set(token) + end + + it "hides token" do + expect(trace.raw).not_to include(token) + end + end + + context 'hides build token' do + let(:token) { 'my_secret_token' } + + before do + build.update(token: token) + trace.set(token) + end + + it "hides token" do + expect(trace.raw).not_to include(token) + end + end + end + + describe '#append' do + before do + trace.set("1234") + end + + it "returns correct trace" do + expect(trace.append("56", 4)).to eq(6) + expect(trace.raw).to eq("123456") + end + + context 'tries to append trace at different offset' do + it "fails with append" do + expect(trace.append("56", 2)).to eq(-4) + expect(trace.raw).to eq("1234") + end + end + + context 'runners token' do + let(:token) { 'my_secret_token' } + + before do + build.project.update(runners_token: token) + trace.append(token, 0) + end + + it "hides token" do + expect(trace.raw).not_to include(token) + end + end + + context 'build token' do + let(:token) { 'my_secret_token' } + + before do + build.update(token: token) + trace.append(token, 0) + end + + it "hides token" do + expect(trace.raw).not_to include(token) + end + end + end + + describe 'trace handling' do + context 'trace does not exist' do + it { expect(trace.exist?).to be(false) } + end + + context 'new trace path is used' do + before do + trace.send(:ensure_directory) + + File.open(trace.send(:default_path), "w") do |file| + file.write("data") + end + end + + it "trace exist" do + expect(trace.exist?).to be(true) + end + + it "can be erased" do + trace.erase! + expect(trace.exist?).to be(false) + end + end + + context 'deprecated path' do + let(:path) { trace.send(:deprecated_path) } + + context 'with valid ci_id' do + before do + build.project.update(ci_id: 1000) + + FileUtils.mkdir_p(File.dirname(path)) + + File.open(path, "w") do |file| + file.write("data") + end + end + + it "trace exist" do + expect(trace.exist?).to be(true) + end + + it "can be erased" do + trace.erase! + expect(trace.exist?).to be(false) + end + end + + context 'without valid ci_id' do + it "does not return deprecated path" do + expect(path).to be_nil + end + end + end + + context 'stored in database' do + before do + build.send(:write_attribute, :trace, "data") + end + + it "trace exist" do + expect(trace.exist?).to be(true) + end + + it "can be erased" do + trace.erase! + expect(trace.exist?).to be(false) + end + + it "returns database data" do + expect(trace.raw).to eq("data") + end + end + end +end |