diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-10-31 21:09:25 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-10-31 21:09:25 +0300 |
commit | 30b8ea126ffffc9bef610d38f8ebcd91bb687aba (patch) | |
tree | 3705b43015a6d3a1fd85864f1fc555383b8e248b /spec | |
parent | a5519693560d1ac4e120e1afd7d806d13a2d09fd (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
21 files changed, 810 insertions, 233 deletions
diff --git a/spec/fixtures/packages/rpm/payload.json b/spec/fixtures/packages/rpm/payload.json index 36f8e904437..ef948c0bb6f 100644 --- a/spec/fixtures/packages/rpm/payload.json +++ b/spec/fixtures/packages/rpm/payload.json @@ -3,6 +3,9 @@ "/usr/bin/test", "/usr/bin/test/hello.sh" ], + "directories": [ + "/usr/bin/test/" + ], "changelogs": [ { "changelogtext": "First build", diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js index 78859525a63..b3e90e34161 100644 --- a/spec/frontend/boards/stores/actions_spec.js +++ b/spec/frontend/boards/stores/actions_spec.js @@ -8,7 +8,6 @@ import { ListType, issuableTypes, BoardType, - listsQuery, DraggableItemTypes, } from 'ee_else_ce/boards/constants'; import issueMoveListMutation from 'ee_else_ce/boards/graphql/issue_move_list.mutation.graphql'; @@ -21,7 +20,7 @@ import { getMoveData, updateListPosition, } from 'ee_else_ce/boards/boards_util'; -import { gqlClient } from '~/boards/graphql'; +import { defaultClient as gqlClient } from '~/graphql_shared/issuable_client'; import destroyBoardListMutation from '~/boards/graphql/board_list_destroy.mutation.graphql'; import issueCreateMutation from '~/boards/graphql/issue_create.mutation.graphql'; import actions from '~/boards/stores/actions'; @@ -318,21 +317,18 @@ describe('fetchLists', () => { }; const variables = { - query: listsQuery[issuableType].query, - variables: { - fullPath: 'gitlab-org', - boardId: fullBoardId, - filters: {}, - isGroup, - isProject, - }, + fullPath: 'gitlab-org', + boardId: fullBoardId, + filters: {}, + isGroup, + isProject, }; jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse); await actions.fetchLists({ commit, state, dispatch }); - expect(gqlClient.query).toHaveBeenCalledWith(variables); + expect(gqlClient.query).toHaveBeenCalledWith(expect.objectContaining({ variables })); }, ); }); diff --git a/spec/frontend/ml/experiment_tracking/components/__snapshots__/experiment_spec.js.snap b/spec/frontend/ml/experiment_tracking/components/__snapshots__/experiment_spec.js.snap new file mode 100644 index 00000000000..2eba8869535 --- /dev/null +++ b/spec/frontend/ml/experiment_tracking/components/__snapshots__/experiment_spec.js.snap @@ -0,0 +1,223 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ShowExperiment with candidates renders correctly 1`] = ` +<div> + <div + class="gl-alert gl-alert-warning" + > + <svg + aria-hidden="true" + class="gl-icon s16 gl-alert-icon" + data-testid="warning-icon" + role="img" + > + <use + href="#warning" + /> + </svg> + + <div + aria-live="assertive" + class="gl-alert-content" + role="alert" + > + <h2 + class="gl-alert-title" + > + Machine Learning Experiment Tracking is in Incubating Phase + </h2> + + <div + class="gl-alert-body" + > + + GitLab incubates features to explore new use cases. These features are updated regularly, and support is limited + + <a + class="gl-link" + href="https://about.gitlab.com/handbook/engineering/incubation/" + rel="noopener noreferrer" + target="_blank" + > + Learn More + </a> + </div> + + <div + class="gl-alert-actions" + > + <a + class="btn gl-alert-action btn-confirm btn-md gl-button" + href="https://gitlab.com/groups/gitlab-org/-/epics/8560" + > + <!----> + + <!----> + + <span + class="gl-button-text" + > + + Feedback and Updates + + </span> + </a> + </div> + </div> + + <button + aria-label="Dismiss" + class="btn gl-dismiss-btn btn-default btn-sm gl-button btn-default-tertiary btn-icon" + type="button" + > + <!----> + + <svg + aria-hidden="true" + class="gl-button-icon gl-icon s16" + data-testid="close-icon" + role="img" + > + <use + href="#close" + /> + </svg> + + <!----> + </button> + </div> + + <h3> + + Experiment Candidates + + </h3> + + <table + aria-busy="false" + aria-colcount="4" + class="table b-table gl-table gl-mt-0!" + role="table" + > + <!----> + <!----> + <thead + class="" + role="rowgroup" + > + <!----> + <tr + class="" + role="row" + > + <th + aria-colindex="1" + class="" + role="columnheader" + scope="col" + > + <div> + L1 Ratio + </div> + </th> + <th + aria-colindex="2" + class="" + role="columnheader" + scope="col" + > + <div> + Rmse + </div> + </th> + <th + aria-colindex="3" + class="" + role="columnheader" + scope="col" + > + <div> + Auc + </div> + </th> + <th + aria-colindex="4" + class="" + role="columnheader" + scope="col" + > + <div> + Mae + </div> + </th> + </tr> + </thead> + <tbody + role="rowgroup" + > + <!----> + <tr + class="" + role="row" + > + <td + aria-colindex="1" + class="" + role="cell" + > + 0.4 + </td> + <td + aria-colindex="2" + class="" + role="cell" + > + 1 + </td> + <td + aria-colindex="3" + class="" + role="cell" + /> + <td + aria-colindex="4" + class="" + role="cell" + /> + </tr> + <tr + class="" + role="row" + > + <td + aria-colindex="1" + class="" + role="cell" + > + 0.5 + </td> + <td + aria-colindex="2" + class="" + role="cell" + /> + <td + aria-colindex="3" + class="" + role="cell" + > + 0.3 + </td> + <td + aria-colindex="4" + class="" + role="cell" + /> + </tr> + <!----> + <!----> + </tbody> + <!----> + </table> +</div> +`; diff --git a/spec/frontend/ml/experiment_tracking/components/experiment_spec.js b/spec/frontend/ml/experiment_tracking/components/experiment_spec.js new file mode 100644 index 00000000000..af722d77532 --- /dev/null +++ b/spec/frontend/ml/experiment_tracking/components/experiment_spec.js @@ -0,0 +1,44 @@ +import { GlAlert } from '@gitlab/ui'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import ShowExperiment from '~/ml/experiment_tracking/components/experiment.vue'; + +describe('ShowExperiment', () => { + let wrapper; + + const createWrapper = (candidates = [], metricNames = [], paramNames = []) => { + return mountExtended(ShowExperiment, { provide: { candidates, metricNames, paramNames } }); + }; + + const findAlert = () => wrapper.findComponent(GlAlert); + + const findEmptyState = () => wrapper.findByText('This Experiment has no logged Candidates'); + + it('shows incubation warning', () => { + wrapper = createWrapper(); + + expect(findAlert().exists()).toBe(true); + }); + + describe('no candidates', () => { + it('shows empty state', () => { + wrapper = createWrapper(); + + expect(findEmptyState().exists()).toBe(true); + }); + }); + + describe('with candidates', () => { + it('renders correctly', () => { + wrapper = createWrapper( + [ + { rmse: 1, l1_ratio: 0.4 }, + { auc: 0.3, l1_ratio: 0.5 }, + ], + ['rmse', 'auc', 'mae'], + ['l1_ratio'], + ); + + expect(wrapper.element).toMatchSnapshot(); + }); + }); +}); diff --git a/spec/frontend/ml/experiment_tracking/components/incubation_alert_spec.js b/spec/frontend/ml/experiment_tracking/components/incubation_alert_spec.js new file mode 100644 index 00000000000..e07a4ed816b --- /dev/null +++ b/spec/frontend/ml/experiment_tracking/components/incubation_alert_spec.js @@ -0,0 +1,27 @@ +import { mount } from '@vue/test-utils'; +import { GlAlert, GlButton } from '@gitlab/ui'; +import IncubationAlert from '~/ml/experiment_tracking/components/incubation_alert.vue'; + +describe('IncubationAlert', () => { + let wrapper; + + const findAlert = () => wrapper.findComponent(GlAlert); + + const findButton = () => wrapper.findComponent(GlButton); + + beforeEach(() => { + wrapper = mount(IncubationAlert); + }); + + it('displays link to issue', () => { + expect(findButton().attributes().href).toBe( + 'https://gitlab.com/groups/gitlab-org/-/epics/8560', + ); + }); + + it('is removed if dismissed', async () => { + await wrapper.find('[aria-label="Dismiss"]').trigger('click'); + + expect(findAlert().exists()).toBe(false); + }); +}); diff --git a/spec/frontend/work_items/components/work_item_assignees_spec.js b/spec/frontend/work_items/components/work_item_assignees_spec.js index 1b204b6fd60..7367212e49f 100644 --- a/spec/frontend/work_items/components/work_item_assignees_spec.js +++ b/spec/frontend/work_items/components/work_item_assignees_spec.js @@ -8,7 +8,7 @@ import { mockTracking } from 'helpers/tracking_helper'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import userSearchQuery from '~/graphql_shared/queries/users_search.query.graphql'; import currentUserQuery from '~/graphql_shared/queries/current_user.query.graphql'; -import { temporaryConfig } from '~/graphql_shared/issuable_client'; +import { config } from '~/graphql_shared/issuable_client'; import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue'; import workItemQuery from '~/work_items/graphql/work_item.query.graphql'; import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql'; @@ -86,7 +86,7 @@ describe('WorkItemAssignees component', () => { ], {}, { - typePolicies: temporaryConfig.cacheConfig.typePolicies, + typePolicies: config.cacheConfig.typePolicies, }, ); diff --git a/spec/frontend/work_items/components/work_item_detail_spec.js b/spec/frontend/work_items/components/work_item_detail_spec.js index aae61b11196..1e22fb42a83 100644 --- a/spec/frontend/work_items/components/work_item_detail_spec.js +++ b/spec/frontend/work_items/components/work_item_detail_spec.js @@ -29,7 +29,7 @@ import workItemTitleSubscription from '~/work_items/graphql/work_item_title.subs import workItemAssigneesSubscription from '~/work_items/graphql/work_item_assignees.subscription.graphql'; import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql'; import updateWorkItemTaskMutation from '~/work_items/graphql/update_work_item_task.mutation.graphql'; -import { temporaryConfig } from '~/graphql_shared/issuable_client'; +import { config } from '~/graphql_shared/issuable_client'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import { mockParent, @@ -101,7 +101,7 @@ describe('WorkItemDetail component', () => { handlers, {}, { - typePolicies: includeWidgets ? temporaryConfig.cacheConfig.typePolicies : {}, + typePolicies: includeWidgets ? config.cacheConfig.typePolicies : {}, }, ), propsData: { isModal, workItemId }, diff --git a/spec/frontend/work_items/components/work_item_milestone_spec.js b/spec/frontend/work_items/components/work_item_milestone_spec.js index 08cdf62ae52..e5713efdbec 100644 --- a/spec/frontend/work_items/components/work_item_milestone_spec.js +++ b/spec/frontend/work_items/components/work_item_milestone_spec.js @@ -9,7 +9,7 @@ import { import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import WorkItemMilestone from '~/work_items/components/work_item_milestone.vue'; -import { resolvers, temporaryConfig } from '~/graphql_shared/issuable_client'; +import { resolvers, config } from '~/graphql_shared/issuable_client'; import createMockApollo from 'helpers/mock_apollo_helper'; import { mockTracking } from 'helpers/tracking_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -72,7 +72,7 @@ describe('WorkItemMilestone component', () => { [[projectMilestonesQuery, searchQueryHandler]], resolvers, { - typePolicies: temporaryConfig.cacheConfig.typePolicies, + typePolicies: config.cacheConfig.typePolicies, }, ); diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index a2e34471324..0b3d400041c 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -332,8 +332,8 @@ RSpec.describe MarkupHelper do context 'when file is Markdown' do let(:extension) { 'md' } - it 'renders using #markdown_unsafe helper method' do - expect(helper).to receive(:markdown_unsafe).with('wiki content', context) + it 'renders using CommonMark method' do + expect(Banzai).to receive(:render).with('wiki content', context) helper.render_wiki_content(wiki_page) end @@ -377,24 +377,16 @@ RSpec.describe MarkupHelper do end end - context 'when file is Kramdown' do + context 'when file is R Markdown' do let(:extension) { 'rmd' } - let(:content) do - <<-EOF -{::options parse_block_html="true" /} - -<div> -FooBar -</div> - EOF - end + let(:content) { '## Header' } - it 'renders using #markdown_unsafe helper method' do - expect(helper).to receive(:markdown_unsafe).with(content, context) + it 'renders using CommonMark method' do + expect(Markup::RenderingService).to receive(:new).and_call_original result = helper.render_wiki_content(wiki_page) - expect(result).to be_empty + expect(result).to include('Header</h2>') end end @@ -424,23 +416,9 @@ FooBar expect(helper.markup('foo.rst', content).encoding.name).to eq('UTF-8') end - it 'delegates to #markdown_unsafe when file name corresponds to Markdown' do - expect(Gitlab::MarkupHelper).to receive(:gitlab_markdown?).with('foo.md').and_return(true) - expect(helper).to receive(:markdown_unsafe).and_return('NOEL') - - expect(helper.markup('foo.md', content)).to eq('NOEL') - end - - it 'delegates to #asciidoc_unsafe when file name corresponds to AsciiDoc' do - expect(Gitlab::MarkupHelper).to receive(:asciidoc?).with('foo.adoc').and_return(true) - expect(helper).to receive(:asciidoc_unsafe).and_return('NOEL') - - expect(helper.markup('foo.adoc', content)).to eq('NOEL') - end - it 'uses passed in rendered content' do expect(Gitlab::MarkupHelper).not_to receive(:gitlab_markdown?) - expect(helper).not_to receive(:markdown_unsafe) + expect(Markup::RenderingService).not_to receive(:execute) expect(helper.markup('foo.md', content, rendered: '<p>NOEL</p>')).to eq('<p>NOEL</p>') end @@ -448,113 +426,18 @@ FooBar it 'defaults to CommonMark' do expect(helper.markup('foo.md', 'x^2')).to include('x^2') end - end - - describe '#markup_unsafe' do - subject { helper.markup_unsafe(file_name, text, context) } - - let_it_be(:project_base) { create(:project, :repository) } - let_it_be(:context) { { project: project_base } } - - let(:file_name) { 'foo.bar' } - let(:text) { 'Noël' } - - context 'when text is missing' do - let(:text) { nil } - - it 'returns an empty string' do - is_expected.to eq('') - end - end - - context 'when rendering takes too long' do - before do - stub_const("MarkupHelper::RENDER_TIMEOUT", 0.1) - allow(Gitlab::OtherMarkup).to receive(:render) { sleep(0.2) } - end - - it 'times out' do - expect(Gitlab::RenderTimeout).to receive(:timeout).and_call_original - expect(Gitlab::ErrorTracking).to receive(:track_exception).with( - instance_of(Timeout::Error), - project_id: project.id, file_name: file_name - ) - - subject - end - - context 'when markup_rendering_timeout is disabled' do - it 'waits until the execution completes' do - stub_feature_flags(markup_rendering_timeout: false) - - expect(Gitlab::RenderTimeout).not_to receive(:timeout) - - subject - end - end - end - - context 'when file is a markdown file' do - let(:file_name) { 'foo.md' } - it 'returns html (rendered by Banzai)' do - expected_html = '<p data-sourcepos="1:1-1:5" dir="auto">Noël</p>' - - expect(Banzai).to receive(:render).with(text, context) { expected_html } - - is_expected.to eq(expected_html) - end - - context 'when renderer returns an error' do - before do - allow(Banzai).to receive(:render).and_raise(StandardError, "An error") - end - - it 'returns html (rendered by ActionView:TextHelper)' do - is_expected.to eq('<p>Noël</p>') - end - - it 'logs the error' do - expect(Gitlab::ErrorTracking).to receive(:track_exception).with( - instance_of(StandardError), - project_id: project.id, file_name: 'foo.md' - ) - - subject - end - end - end - - context 'when file is asciidoc file' do - let(:file_name) { 'foo.adoc' } - - it 'returns html (rendered by Gitlab::Asciidoc)' do - expected_html = "<div>\n<p>Noël</p>\n</div>" - - expect(Gitlab::Asciidoc).to receive(:render).with(text, context) { expected_html } - - is_expected.to eq(expected_html) - end - end - - context 'when file is a regular text file' do - let(:file_name) { 'foo.txt' } - - it 'returns html (rendered by ActionView::TagHelper)' do - is_expected.to eq('<pre class="plain-readme">Noël</pre>') - end - end - - context 'when file has an unknown type' do - let(:file_name) { 'foo.tex' } + it 'sets additional context for Asciidoc' do + context = {} + assign(:commit, commit) + assign(:ref, 'ref') + assign(:path, 'path') - it 'returns html (rendered by Gitlab::OtherMarkup)' do - expected_html = 'Noël' + expect(Gitlab::Asciidoc).to receive(:render) - expect(Gitlab::OtherMarkup).to receive(:render).with(file_name, text, context) { expected_html } + helper.markup('foo.adoc', content, context) - is_expected.to eq(expected_html) - end + expect(context).to include(commit: commit, ref: 'ref', requested_path: 'path') end end diff --git a/spec/helpers/projects/ml/experiments_helper_spec.rb b/spec/helpers/projects/ml/experiments_helper_spec.rb new file mode 100644 index 00000000000..e4421ff7606 --- /dev/null +++ b/spec/helpers/projects/ml/experiments_helper_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'rspec' + +require 'spec_helper' +require 'mime/types' + +RSpec.describe Projects::Ml::ExperimentsHelper do + let_it_be(:project) { build(:project, :private) } + let_it_be(:experiment) { build(:ml_experiments, user_id: project.creator, project: project) } + let_it_be(:candidates) do + create_list(:ml_candidates, 2, experiment: experiment, user: project.creator).tap do |c| + c[0].params.create!([{ name: 'param1', value: 'p1' }, { name: 'param2', value: 'p2' }]) + c[0].metrics.create!( + [{ name: 'metric1', value: 0.1 }, { name: 'metric2', value: 0.2 }, { name: 'metric3', value: 0.3 }] + ) + + c[1].params.create!([{ name: 'param2', value: 'p3' }, { name: 'param3', value: 'p4' }]) + c[1].metrics.create!(name: 'metric3', value: 0.4) + end + end + + describe '#candidates_table_items' do + subject { helper.candidates_table_items(candidates) } + + it 'creates the correct model for the table' do + expected_value = [ + { 'param1' => 'p1', 'param2' => 'p2', 'metric1' => '0.1000', 'metric2' => '0.2000', 'metric3' => '0.3000' }, + { 'param2' => 'p3', 'param3' => 'p4', 'metric3' => '0.4000' } + ] + + expect(Gitlab::Json.parse(subject)).to match_array(expected_value) + end + end + + describe '#unique_logged_names' do + context 'when for params' do + subject { Gitlab::Json.parse(helper.unique_logged_names(candidates, &:params)) } + + it { is_expected.to match_array(%w[param1 param2 param3]) } + end + + context 'when latest_metrics is passed' do + subject { Gitlab::Json.parse(helper.unique_logged_names(candidates, &:latest_metrics)) } + + it { is_expected.to match_array(%w[metric1 metric2 metric3]) } + end + end +end diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb index 616fe15c1a6..47745eaa4af 100644 --- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -53,6 +53,18 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do expect(directives['child_src']).to eq("#{directives['frame_src']} #{directives['worker_src']}") end + describe 'the images-src directive' do + it 'can be loaded from anywhere' do + expect(directives['img_src']).to include('http: https:') + end + end + + describe 'the media-src directive' do + it 'can be loaded from anywhere' do + expect(directives['media_src']).to include('http: https:') + end + end + context 'adds all websocket origins to support Safari' do it 'with insecure domain' do stub_config_setting(host: 'example.com', https: false) diff --git a/spec/models/ml/candidate_metric_spec.rb b/spec/models/ml/candidate_metric_spec.rb index 5ee6030fb8e..9f9a6e8e3ba 100644 --- a/spec/models/ml/candidate_metric_spec.rb +++ b/spec/models/ml/candidate_metric_spec.rb @@ -6,4 +6,17 @@ RSpec.describe Ml::CandidateMetric do describe 'associations' do it { is_expected.to belong_to(:candidate) } end + + describe 'scope :latest' do + let_it_be(:candidate) { create(:ml_candidates) } + let!(:metric1) { create(:ml_candidate_metrics, candidate: candidate) } + let!(:metric2) { create(:ml_candidate_metrics, candidate: candidate ) } + let!(:metric3) { create(:ml_candidate_metrics, name: metric1.name, candidate: candidate) } + + subject { described_class.latest } + + it 'fetches only the last metric for the name' do + expect(subject).to match_array([metric2, metric3] ) + end + end end diff --git a/spec/models/ml/candidate_spec.rb b/spec/models/ml/candidate_spec.rb index 84e0e6be199..d08dc35b358 100644 --- a/spec/models/ml/candidate_spec.rb +++ b/spec/models/ml/candidate_spec.rb @@ -46,4 +46,26 @@ RSpec.describe Ml::Candidate, factory_default: :keep do it { is_expected.to be_nil } end end + + describe "#latest_metrics" do + let_it_be(:candidate2) { create(:ml_candidates, experiment: candidate.experiment) } + let!(:metric1) { create(:ml_candidate_metrics, candidate: candidate2) } + let!(:metric2) { create(:ml_candidate_metrics, candidate: candidate2 ) } + let!(:metric3) { create(:ml_candidate_metrics, name: metric1.name, candidate: candidate2) } + + subject { candidate2.latest_metrics } + + it 'fetches only the last metric for the name' do + expect(subject).to match_array([metric2, metric3] ) + end + end + + describe "#including_metrics_and_params" do + subject { described_class.including_metrics_and_params.find_by(id: candidate.id) } + + it 'loads latest metrics and params', :aggregate_failures do + expect(subject.association_cached?(:latest_metrics)).to be(true) + expect(subject.association_cached?(:params)).to be(true) + end + end end diff --git a/spec/requests/projects/ml/experiments_controller_spec.rb b/spec/requests/projects/ml/experiments_controller_spec.rb new file mode 100644 index 00000000000..7be5899fcbe --- /dev/null +++ b/spec/requests/projects/ml/experiments_controller_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::Ml::ExperimentsController do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { project.first_owner } + let_it_be(:basic_params) { { namespace_id: project.namespace.to_param, project_id: project } } + let_it_be(:experiment) do + create(:ml_experiments, project: project, user: user).tap do |e| + create(:ml_candidates, experiment: e, user: user) + end + end + + let(:params) { basic_params } + let(:ff_value) { true } + let(:threshold) { 4 } + + before do + stub_feature_flags(ml_experiment_tracking: ff_value) + + sign_in(user) + end + + shared_examples '404 if feature flag disabled' do + context 'when :ml_experiment_tracking disabled' do + let(:ff_value) { false } + + it 'is 404' do + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + describe 'GET index' do + before do + list_experiments + end + + it 'renders the template' do + expect(response).to render_template('projects/ml/experiments/index') + end + + it 'does not perform N+1 sql queries' do + control_count = ActiveRecord::QueryRecorder.new { list_experiments } + + create_list(:ml_experiments, 2, project: project, user: user) + + expect { list_experiments }.not_to exceed_all_query_limit(control_count).with_threshold(threshold) + end + + it_behaves_like '404 if feature flag disabled' + end + + describe 'GET show' do + let(:params) { basic_params.merge(id: experiment.iid) } + + before do + show_experiment + end + + it 'renders the template' do + expect(response).to render_template('projects/ml/experiments/show') + end + + it 'does not perform N+1 sql queries' do + control_count = ActiveRecord::QueryRecorder.new { show_experiment } + + create_list(:ml_candidates, 2, :with_metrics_and_params, experiment: experiment) + + expect { show_experiment }.not_to exceed_all_query_limit(control_count).with_threshold(threshold) + end + + it_behaves_like '404 if feature flag disabled' + end + + private + + def show_experiment + get project_ml_experiment_path(project, experiment.iid), params: params + end + + def list_experiments + get project_ml_experiments_path(project), params: params + end +end diff --git a/spec/scripts/trigger-build_spec.rb b/spec/scripts/trigger-build_spec.rb index ac8e3c7797c..758336d251d 100644 --- a/spec/scripts/trigger-build_spec.rb +++ b/spec/scripts/trigger-build_spec.rb @@ -337,6 +337,29 @@ RSpec.describe Trigger do it 'sets GITLAB_ASSETS_TAG to CI_COMMIT_SHA' do expect(subject.variables['GITLAB_ASSETS_TAG']).to eq(env['CI_COMMIT_SHA']) end + + context 'when cached-assets-hash.txt does not exist' do + before do + expect(File).to receive(:exist?).with('cached-assets-hash.txt').and_return(false) + end + + it 'sets GITLAB_ASSETS_TAG to CI_COMMIT_SHA' do + expect(subject.variables['GITLAB_ASSETS_TAG']).to eq(env['CI_COMMIT_SHA']) + end + end + + context 'when cached-assets-hash.txt exists' do + before do + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:read).and_call_original + expect(File).to receive(:exist?).with('cached-assets-hash.txt').and_return(true) + expect(File).to receive(:read).with('cached-assets-hash.txt').and_return("42") + end + + it 'sets GITLAB_ASSETS_TAG to CI_COMMIT_SHA' do + expect(subject.variables['GITLAB_ASSETS_TAG']).to eq("assets-hash-42") + end + end end end diff --git a/spec/services/markup/rendering_service_spec.rb b/spec/services/markup/rendering_service_spec.rb new file mode 100644 index 00000000000..a5711a8cbc4 --- /dev/null +++ b/spec/services/markup/rendering_service_spec.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Markup::RenderingService do + describe '#execute' do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) do + user = create(:user, username: 'gfm') + project.add_maintainer(user) + user + end + + let_it_be(:context) { { project: project } } + let_it_be(:postprocess_context) { { current_user: user } } + + let(:file_name) { nil } + let(:text) { 'Noël' } + + subject do + described_class + .new(text, file_name: file_name, context: context, postprocess_context: postprocess_context) + .execute + end + + context 'when text is missing' do + let(:text) { nil } + + it 'returns an empty string' do + is_expected.to eq('') + end + end + + context 'when file_name is missing' do + it 'returns html (rendered by Banzai)' do + expected_html = '<p data-sourcepos="1:1-1:5" dir="auto">Noël</p>' + + expect(Banzai).to receive(:render).with(text, context) { expected_html } + + is_expected.to eq(expected_html) + end + end + + context 'when postprocess_context is missing' do + let(:file_name) { 'foo.txt' } + let(:postprocess_context) { nil } + + it 'returns html (rendered by Banzai)' do + expected_html = '<pre class="plain-readme">Noël</pre>' + + expect(Banzai).not_to receive(:post_process) { expected_html } + + is_expected.to eq(expected_html) + end + end + + context 'when rendered context is present' do + let(:rendered) { 'rendered text' } + let(:file_name) { 'foo.md' } + + it 'returns an empty string' do + context[:rendered] = rendered + + is_expected.to eq(rendered) + end + end + + context 'when file is a markdown file' do + let(:file_name) { 'foo.md' } + + it 'returns html (rendered by Banzai)' do + expected_html = '<p data-sourcepos="1:1-1:5" dir="auto">Noël</p>' + + expect(Banzai).to receive(:render).with(text, context) { expected_html } + + is_expected.to eq(expected_html) + end + + context 'when renderer returns an error' do + before do + allow(Banzai).to receive(:render).and_raise(StandardError, "An error") + end + + it 'returns html (rendered by ActionView:TextHelper)' do + is_expected.to eq('<p>Noël</p>') + end + + it 'logs the error' do + expect(Gitlab::ErrorTracking).to receive(:track_exception).with( + instance_of(StandardError), + project_id: context[:project].id, file_name: 'foo.md' + ) + + subject + end + end + end + + context 'when file is asciidoc file' do + let(:file_name) { 'foo.adoc' } + + it 'returns html (rendered by Gitlab::Asciidoc)' do + expected_html = "<div>\n<p>Noël</p>\n</div>" + + expect(Gitlab::Asciidoc).to receive(:render).with(text, context) { expected_html } + + is_expected.to eq(expected_html) + end + end + + context 'when file is a regular text file' do + let(:file_name) { 'foo.txt' } + + it 'returns html (rendered by ActionView::TagHelper)' do + is_expected.to eq('<pre class="plain-readme">Noël</pre>') + end + end + + context 'when file has an unknown type' do + let(:file_name) { 'foo.tex' } + + it 'returns html (rendered by Gitlab::OtherMarkup)' do + expected_html = 'Noël' + + expect(Gitlab::OtherMarkup).to receive(:render).with(file_name, text, context) { expected_html } + + is_expected.to eq(expected_html) + end + end + + context 'when rendering takes too long' do + let(:file_name) { 'foo.bar' } + + before do + stub_const("Markup::RenderingService::RENDER_TIMEOUT", 0.1) + allow(Gitlab::OtherMarkup).to receive(:render) do + sleep(0.2) + text + end + end + + it 'times out' do + expect(Gitlab::RenderTimeout).to receive(:timeout).and_call_original + expect(Gitlab::ErrorTracking).to receive(:track_exception).with( + instance_of(Timeout::Error), + project_id: context[:project].id, file_name: file_name + ) + + is_expected.to eq("<p>#{text}</p>") + end + + context 'when markup_rendering_timeout is disabled' do + it 'waits until the execution completes' do + stub_feature_flags(markup_rendering_timeout: false) + + expect(Gitlab::RenderTimeout).not_to receive(:timeout) + + is_expected.to eq(text) + end + end + end + end +end diff --git a/spec/services/packages/rpm/repository_metadata/build_filelist_xml_service_spec.rb b/spec/services/packages/rpm/repository_metadata/build_filelist_xml_service_spec.rb new file mode 100644 index 00000000000..d93d6ab9fcb --- /dev/null +++ b/spec/services/packages/rpm/repository_metadata/build_filelist_xml_service_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe Packages::Rpm::RepositoryMetadata::BuildFilelistXmlService do + describe '#execute' do + subject { described_class.new(data).execute } + + include_context 'with rpm package data' + + let(:data) { xml_update_params } + let(:file_xpath) { "//package/file" } + + it 'adds all file nodes' do + result = subject + + expect(result.xpath(file_xpath).count).to eq(data[:files].count) + end + + describe 'setting type attribute' do + context 'when all files are directories' do + let(:dirs) do + 3.times.map { generate_directory } # rubocop:disable Performance/TimesMap + end + + let(:files) do + 5.times.map { FFaker::Filesystem.file_name(dirs.sample) } # rubocop:disable Performance/TimesMap + end + + let(:data) do + { + directories: dirs.map { "#{_1}/" }, # Add trailing slash as in original package + files: dirs + files + } + end + + it 'set dir type attribute for directories only' do + result = subject + + result.xpath(file_xpath).each do |tag| + if dirs.include?(tag.content) + expect(tag.attributes['type']&.value).to eq('dir') + else + expect(tag.attributes['type']).to be_nil + end + end + end + end + + def generate_directory + FFaker::Lorem.words(3).join('/') + end + end + end +end diff --git a/spec/services/packages/rpm/repository_metadata/build_other_xml_service_spec.rb b/spec/services/packages/rpm/repository_metadata/build_other_xml_service_spec.rb index 19080b8dfd0..201f9e67ce9 100644 --- a/spec/services/packages/rpm/repository_metadata/build_other_xml_service_spec.rb +++ b/spec/services/packages/rpm/repository_metadata/build_other_xml_service_spec.rb @@ -5,24 +5,22 @@ RSpec.describe Packages::Rpm::RepositoryMetadata::BuildOtherXmlService do describe '#execute' do subject { described_class.new(data).execute } - context 'when updating existing xml' do - include_context 'with rpm package data' + include_context 'with rpm package data' - let(:data) { xml_update_params } - let(:changelog_xpath) { "//package/changelog" } + let(:data) { xml_update_params } + let(:changelog_xpath) { "//package/changelog" } - it 'adds all changelog nodes' do - result = subject + it 'adds all changelog nodes' do + result = subject - expect(result.xpath(changelog_xpath).count).to eq(data[:changelogs].count) - end + expect(result.xpath(changelog_xpath).count).to eq(data[:changelogs].count) + end - it 'set required date attribute' do - result = subject + it 'set required date attribute' do + result = subject - data[:changelogs].each do |changelog| - expect(result.at("#{changelog_xpath}[@date=\"#{changelog[:changelogtime]}\"]")).not_to be_nil - end + data[:changelogs].each do |changelog| + expect(result.at("#{changelog_xpath}[@date=\"#{changelog[:changelogtime]}\"]")).not_to be_nil end end end diff --git a/spec/services/packages/rpm/repository_metadata/build_primary_xml_service_spec.rb b/spec/services/packages/rpm/repository_metadata/build_primary_xml_service_spec.rb index 902844c196a..9bbfa5c9863 100644 --- a/spec/services/packages/rpm/repository_metadata/build_primary_xml_service_spec.rb +++ b/spec/services/packages/rpm/repository_metadata/build_primary_xml_service_spec.rb @@ -5,20 +5,18 @@ RSpec.describe Packages::Rpm::RepositoryMetadata::BuildPrimaryXmlService do describe '#execute' do subject { described_class.new(data).execute } - context 'when updating existing xml' do - include_context 'with rpm package data' + include_context 'with rpm package data' - let(:data) { xml_update_params } - let(:required_text_only_attributes) { %i[description summary arch name] } + let(:data) { xml_update_params } + let(:required_text_only_attributes) { %i[description summary arch name] } - it 'adds node with required_text_only_attributes' do - result = subject + it 'adds node with required_text_only_attributes' do + result = subject - required_text_only_attributes.each do |attribute| - expect( - result.at("//package/#{attribute}").text - ).to eq(data[attribute]) - end + required_text_only_attributes.each do |attribute| + expect( + result.at("//package/#{attribute}").text + ).to eq(data[attribute]) end end end diff --git a/spec/services/packages/rpm/repository_metadata/update_xml_service_spec.rb b/spec/services/packages/rpm/repository_metadata/update_xml_service_spec.rb index 01991e3928f..e351392ba1c 100644 --- a/spec/services/packages/rpm/repository_metadata/update_xml_service_spec.rb +++ b/spec/services/packages/rpm/repository_metadata/update_xml_service_spec.rb @@ -37,6 +37,16 @@ RSpec.describe Packages::Rpm::RepositoryMetadata::UpdateXmlService do end end + shared_context 'with filelist xml file data' do + let(:filename) { :filelist } + let(:empty_xml) do + <<~XML + <?xml version="1.0" encoding="UTF-8"?> + <filelists xmlns="http://linux.duke.edu/metadata/filelists" packages="0"/> + XML + end + end + context 'when building empty xml' do shared_examples 'generating empty xml' do it 'generate expected xml' do @@ -57,6 +67,12 @@ RSpec.describe Packages::Rpm::RepositoryMetadata::UpdateXmlService do it_behaves_like 'generating empty xml' end + + context "for 'filelist' xml file" do + include_context 'with filelist xml file data' + + it_behaves_like 'generating empty xml' + end end context 'when updating xml file' do @@ -150,6 +166,12 @@ RSpec.describe Packages::Rpm::RepositoryMetadata::UpdateXmlService do it_behaves_like 'updating rpm xml file' end + + context "for 'filelist' xml file" do + include_context 'with filelist xml file data' + + it_behaves_like 'updating rpm xml file' + end end end end diff --git a/spec/tooling/rspec_flaky/flaky_example_spec.rb b/spec/tooling/rspec_flaky/flaky_example_spec.rb index 03436ee1cbd..c83af7e4909 100644 --- a/spec/tooling/rspec_flaky/flaky_example_spec.rb +++ b/spec/tooling/rspec_flaky/flaky_example_spec.rb @@ -9,30 +9,13 @@ RSpec.describe RspecFlaky::FlakyExample, :aggregate_failures do include ActiveSupport::Testing::TimeHelpers include StubENV - let(:flaky_example_attrs) do + let(:example_attrs) do { example_id: 'spec/foo/bar_spec.rb:2', file: 'spec/foo/bar_spec.rb', line: 2, description: 'hello world', - first_flaky_at: 1234, - last_flaky_at: 2345, - last_flaky_job: 'https://gitlab.com/gitlab-org/gitlab-foss/-/jobs/12', - last_attempts_count: 2, - flaky_reports: 1 - } - end - - let(:example_attrs) do - { - uid: 'abc123', - example_id: flaky_example_attrs[:example_id], - file: flaky_example_attrs[:file], - line: flaky_example_attrs[:line], - description: flaky_example_attrs[:description], - status: 'passed', - exception: 'BOOM!', - attempts: flaky_example_attrs[:last_attempts_count] + last_attempts_count: 2 } end @@ -48,18 +31,18 @@ RSpec.describe RspecFlaky::FlakyExample, :aggregate_failures do it 'returns valid attributes' do attrs = flaky_example.to_h - expect(attrs[:uid]).to eq(flaky_example_attrs[:uid]) - expect(attrs[:file]).to eq(flaky_example_attrs[:file]) - expect(attrs[:line]).to eq(flaky_example_attrs[:line]) - expect(attrs[:description]).to eq(flaky_example_attrs[:description]) + expect(attrs[:uid]).to eq(example_attrs[:uid]) + expect(attrs[:file]).to eq(example_attrs[:file]) + expect(attrs[:line]).to eq(example_attrs[:line]) + expect(attrs[:description]).to eq(example_attrs[:description]) expect(attrs[:first_flaky_at]).to eq(expected_first_flaky_at) expect(attrs[:last_flaky_at]).to eq(expected_last_flaky_at) - expect(attrs[:last_attempts_count]).to eq(flaky_example_attrs[:last_attempts_count]) + expect(attrs[:last_attempts_count]).to eq(example_attrs[:last_attempts_count]) expect(attrs[:flaky_reports]).to eq(expected_flaky_reports) end end - context 'when given an Example hash' do + context 'when given an Example.to_h' do it_behaves_like 'a valid FlakyExample instance' do let(:args) { example_attrs } let(:expected_first_flaky_at) { Time.now } @@ -67,18 +50,9 @@ RSpec.describe RspecFlaky::FlakyExample, :aggregate_failures do let(:expected_flaky_reports) { 0 } end end - - context 'when given a FlakyExample hash' do - it_behaves_like 'a valid FlakyExample instance' do - let(:args) { flaky_example_attrs } - let(:expected_flaky_reports) { flaky_example_attrs[:flaky_reports] } - let(:expected_first_flaky_at) { flaky_example_attrs[:first_flaky_at] } - let(:expected_last_flaky_at) { flaky_example_attrs[:last_flaky_at] } - end - end end - describe '#update_flakiness!' do + describe '#update!' do shared_examples 'an up-to-date FlakyExample instance' do let(:flaky_example) { described_class.new(args) } @@ -86,18 +60,18 @@ RSpec.describe RspecFlaky::FlakyExample, :aggregate_failures do args[:first_flaky_at] = nil freeze_time do - flaky_example.update_flakiness! + flaky_example.update!(example_attrs) expect(flaky_example.to_h[:first_flaky_at]).to eq(Time.now) end end it 'maintains the first_flaky_at if exists' do - flaky_example.update_flakiness! + flaky_example.update!(example_attrs) expected_first_flaky_at = flaky_example.to_h[:first_flaky_at] travel_to(Time.now + 42) do - flaky_example.update_flakiness! + flaky_example.update!(example_attrs) expect(flaky_example.to_h[:first_flaky_at]).to eq(expected_first_flaky_at) end end @@ -105,7 +79,7 @@ RSpec.describe RspecFlaky::FlakyExample, :aggregate_failures do it 'updates the last_flaky_at' do travel_to(Time.now + 42) do the_future = Time.now - flaky_example.update_flakiness! + flaky_example.update!(example_attrs) expect(flaky_example.to_h[:last_flaky_at]).to eq(the_future) end @@ -114,16 +88,15 @@ RSpec.describe RspecFlaky::FlakyExample, :aggregate_failures do it 'updates the flaky_reports' do expected_flaky_reports = flaky_example.to_h[:first_flaky_at] ? flaky_example.to_h[:flaky_reports] + 1 : 1 - expect { flaky_example.update_flakiness! }.to change { flaky_example.to_h[:flaky_reports] }.by(1) + expect { flaky_example.update!(example_attrs) }.to change { flaky_example.to_h[:flaky_reports] }.by(1) expect(flaky_example.to_h[:flaky_reports]).to eq(expected_flaky_reports) end - context 'when passed a :last_attempts_count' do - it 'updates the last_attempts_count' do - flaky_example.update_flakiness!(last_attempts_count: 42) + it 'updates the last_attempts_count' do + example_attrs[:last_attempts_count] = 42 + flaky_example.update!(example_attrs) - expect(flaky_example.to_h[:last_attempts_count]).to eq(42) - end + expect(flaky_example.to_h[:last_attempts_count]).to eq(42) end context 'when run on the CI' do @@ -134,7 +107,7 @@ RSpec.describe RspecFlaky::FlakyExample, :aggregate_failures do end it 'updates the last_flaky_job' do - flaky_example.update_flakiness! + flaky_example.update!(example_attrs) expect(flaky_example.to_h[:last_flaky_job]).to eq(job_url) end @@ -146,12 +119,6 @@ RSpec.describe RspecFlaky::FlakyExample, :aggregate_failures do let(:args) { example_attrs } end end - - context 'when given a FlakyExample hash' do - it_behaves_like 'an up-to-date FlakyExample instance' do - let(:args) { flaky_example_attrs } - end - end end describe '#to_h', :freeze_time do @@ -160,7 +127,7 @@ RSpec.describe RspecFlaky::FlakyExample, :aggregate_failures do it 'returns a valid hash' do flaky_example = described_class.new(args) - final_hash = flaky_example_attrs.merge(additional_attrs) + final_hash = example_attrs.merge(additional_attrs) expect(flaky_example.to_h).to eq(final_hash) end @@ -175,11 +142,5 @@ RSpec.describe RspecFlaky::FlakyExample, :aggregate_failures do end end end - - context 'when given a FlakyExample hash' do - let(:args) { flaky_example_attrs } - - it_behaves_like 'a valid FlakyExample hash' - end end end |