diff options
33 files changed, 555 insertions, 366 deletions
@@ -133,7 +133,7 @@ gem 'acts-as-taggable-on', '~> 4.0' # Background jobs gem 'sidekiq', '~> 4.2' -gem 'sidekiq-cron', '~> 0.4.0' +gem 'sidekiq-cron', '~> 0.4.4' gem 'redis-namespace', '~> 1.5.2' gem 'sidekiq-limit_fetch', '~> 3.4' @@ -309,6 +309,8 @@ group :development, :test do gem 'knapsack', '~> 1.11.0' gem 'activerecord_sane_schema_dumper', '0.2' + + gem 'stackprof', '~> 0.2.10' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index bf9702b2562..1db0e466164 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -614,7 +614,8 @@ GEM rubyntlm (0.5.2) rubypants (0.2.0) rubyzip (1.2.0) - rufus-scheduler (3.1.10) + rufus-scheduler (3.3.0) + tzinfo rugged (0.24.0) safe_yaml (1.0.4) sanitize (2.1.0) @@ -650,10 +651,10 @@ GEM connection_pool (~> 2.2, >= 2.2.0) rack-protection (~> 1.5) redis (~> 3.2, >= 3.2.1) - sidekiq-cron (0.4.0) + sidekiq-cron (0.4.4) redis-namespace (>= 1.5.2) rufus-scheduler (>= 2.0.24) - sidekiq (>= 4.0.0) + sidekiq (>= 4.2.1) sidekiq-limit_fetch (3.4.0) sidekiq (>= 4) simplecov (0.12.0) @@ -691,6 +692,7 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) + stackprof (0.2.10) state_machines (0.4.0) state_machines-activemodel (0.4.0) activemodel (>= 4.1, < 5.1) @@ -925,7 +927,7 @@ DEPENDENCIES sham_rack (~> 1.3.6) shoulda-matchers (~> 2.8.0) sidekiq (~> 4.2) - sidekiq-cron (~> 0.4.0) + sidekiq-cron (~> 0.4.4) sidekiq-limit_fetch (~> 3.4) simplecov (= 0.12.0) slack-notifier (~> 1.2.0) @@ -937,6 +939,7 @@ DEPENDENCIES spring-commands-teaspoon (~> 0.0.2) sprockets (~> 3.7.0) sprockets-es6 (~> 0.9.2) + stackprof (~> 0.2.10) state_machines-activerecord (~> 0.4.0) sys-filesystem (~> 1.1.6) teaspoon (~> 1.1.0) diff --git a/README.md b/README.md index c9bb5af6da2..61204630fd2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # GitLab [![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) -[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](http://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) +[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 index b9675f50e31..0d85e1a4678 100644 --- a/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 +++ b/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 @@ -10,10 +10,15 @@ }, template: ` <span class="total-time"> - <template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template> - <template v-if="time.hours">{{ time.hours }} <span>hr</span></template> - <template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template> - <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template> + <template v-if="Object.keys(time).length"> + <template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template> + <template v-if="time.hours">{{ time.hours }} <span>hr</span></template> + <template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template> + <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template> + </template> + <template v-else> + -- + </template> </span> `, }); diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index a2225cc8343..f47df8b623b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -325,16 +325,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.update(merge_error: nil) if params[:merge_when_build_succeeds].present? - unless @merge_request.pipeline + unless @merge_request.head_pipeline @status = :failed return end - if @merge_request.pipeline.active? + if @merge_request.head_pipeline.active? MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) .execute(@merge_request) @status = :merge_when_build_succeeds - elsif @merge_request.pipeline.success? + elsif @merge_request.head_pipeline.success? # This can be triggered when a user clicks the auto merge button while # the tests finish at about the same time MergeWorker.perform_async(@merge_request.id, current_user.id, params) @@ -398,7 +398,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def ci_status - pipeline = @merge_request.pipeline + pipeline = @merge_request.head_pipeline + if pipeline status = pipeline.status coverage = pipeline.try(:coverage) @@ -534,7 +535,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def define_widget_vars - @pipeline = @merge_request.pipeline + @pipeline = @merge_request.head_pipeline end def define_commit_vars @@ -563,7 +564,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_pipelines_vars @pipelines = @merge_request.all_pipelines - @pipeline = @merge_request.pipeline + @pipeline = @merge_request.head_pipeline @statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0 end @@ -631,7 +632,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def merge_when_build_succeeds_active? params[:merge_when_build_succeeds].present? && - @merge_request.pipeline && @merge_request.pipeline.active? + @merge_request.head_pipeline && @merge_request.head_pipeline.active? end def build_merge_request diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 4294a10e9e3..fabbf97d4db 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -317,7 +317,7 @@ module Ci def merge_requests @merge_requests ||= project.merge_requests .where(source_branch: self.ref) - .select { |merge_request| merge_request.pipeline.try(:id) == self.id } + .select { |merge_request| merge_request.head_pipeline.try(:id) == self.id } end private diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 38d8c15e6b0..64990f8134e 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -678,7 +678,7 @@ class MergeRequest < ActiveRecord::Base def mergeable_ci_state? return true unless project.only_allow_merge_if_build_succeeds? - !pipeline || pipeline.success? || pipeline.skipped? + !head_pipeline || head_pipeline.success? || head_pipeline.skipped? end def environments @@ -774,10 +774,10 @@ class MergeRequest < ActiveRecord::Base commits.map(&:sha) end - def pipeline + def head_pipeline return unless diff_head_sha && source_project - @pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha) + @head_pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha) end def all_pipelines diff --git a/app/serializers/analytics_build_entity.rb b/app/serializers/analytics_build_entity.rb index abefcd5cc02..a0db5b8f0f4 100644 --- a/app/serializers/analytics_build_entity.rb +++ b/app/serializers/analytics_build_entity.rb @@ -13,7 +13,7 @@ class AnalyticsBuildEntity < Grape::Entity end expose :duration, as: :total_time do |build| - distance_of_time_as_hash(build.duration.to_f) + build.duration ? distance_of_time_as_hash(build.duration.to_f) : {} end expose :branch do diff --git a/app/serializers/entity_date_helper.rb b/app/serializers/entity_date_helper.rb index 918abba8d99..9607ad55a8b 100644 --- a/app/serializers/entity_date_helper.rb +++ b/app/serializers/entity_date_helper.rb @@ -2,6 +2,8 @@ module EntityDateHelper include ActionView::Helpers::DateHelper def interval_in_words(diff) + return 'Not started' unless diff + "#{distance_of_time_in_words(Time.now, diff)} ago" end diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index cde856b0186..e3bc9847200 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -45,9 +45,15 @@ module Ci return error('No builds for this pipeline.') end - pipeline.save - pipeline.process! - pipeline + Ci::Pipeline.transaction do + pipeline.save + + Ci::CreatePipelineBuildsService + .new(project, current_user) + .execute(pipeline) + end + + pipeline.tap(&:process!) end private diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index 8face432d97..2e028c44d8b 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -5,10 +5,7 @@ module Ci def execute(pipeline) @pipeline = pipeline - # This method will ensure that our pipeline does have all builds for all stages created - if created_builds.empty? - create_builds! - end + ensure_created_builds! # TODO, remove me in 9.0 new_builds = stage_indexes_of_created_builds.map do |index| @@ -22,10 +19,6 @@ module Ci private - def create_builds! - Ci::CreatePipelineBuildsService.new(project, current_user).execute(pipeline) - end - def process_stage(index) current_status = status_for_prior_stages(index) @@ -76,5 +69,18 @@ module Ci def created_builds pipeline.builds.created end + + # This method is DEPRECATED and should be removed in 9.0. + # + # We need it to maintain backwards compatibility with previous versions + # when builds were not created within one transaction with the pipeline. + # + def ensure_created_builds! + return if created_builds.any? + + Ci::CreatePipelineBuildsService + .new(project, current_user) + .execute(pipeline) + end end end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 58f69a41e14..800fd39c424 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -55,7 +55,7 @@ module MergeRequests def pipeline_merge_requests(pipeline) merge_requests_for(pipeline.ref).each do |merge_request| - next unless pipeline == merge_request.pipeline + next unless pipeline == merge_request.head_pipeline yield merge_request end @@ -63,7 +63,7 @@ module MergeRequests def commit_status_merge_requests(commit_status) merge_requests_for(commit_status.ref).each do |merge_request| - pipeline = merge_request.pipeline + pipeline = merge_request.head_pipeline next unless pipeline next unless pipeline.sha == commit_status.sha diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml index 747bfa554cb..d48923b422a 100644 --- a/app/views/projects/issues/_merge_requests.html.haml +++ b/app/views/projects/issues/_merge_requests.html.haml @@ -2,12 +2,12 @@ %h2.merge-requests-title = pluralize(@merge_requests.count, 'Related Merge Request') %ul.unstyled-list.related-merge-requests - - has_any_ci = @merge_requests.any?(&:pipeline) + - has_any_ci = @merge_requests.any?(&:head_pipeline) - @merge_requests.each do |merge_request| %li %span.merge-request-ci-status - - if merge_request.pipeline - = render_pipeline_status(merge_request.pipeline) + - if merge_request.head_pipeline + = render_pipeline_status(merge_request.head_pipeline) - elsif has_any_ci = icon('blank fw') %span.merge-request-id diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 9ffcc48eb80..fa189ae62d8 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -15,9 +15,9 @@ = icon('ban') CLOSED - - if merge_request.pipeline + - if merge_request.head_pipeline %li - = render_pipeline_status(merge_request.pipeline) + = render_pipeline_status(merge_request.head_pipeline) - if merge_request.open? && merge_request.broken? %li diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index 608fdf1c5f5..a8918c85dde 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -14,7 +14,7 @@ ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", ci_environments_status_url: "#{ci_environments_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", gitlab_icon: "#{asset_path 'gitlab_logo.png'}", - ci_status: "#{@merge_request.pipeline ? @merge_request.pipeline.status : ''}", + ci_status: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.status : ''}", ci_message: { normal: "Build {{status}} for \"{{title}}\"", preparing: "{{status}} build for \"{{title}}\"" diff --git a/bin/rspec-stackprof b/bin/rspec-stackprof new file mode 100755 index 00000000000..df79feb201d --- /dev/null +++ b/bin/rspec-stackprof @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby + +require 'stackprof' +$:.unshift 'spec' +require 'rails_helper' + +filename = ARGV[0].split('/').last +interval = ENV.fetch('INTERVAL', 1000).to_i +limit = ENV.fetch('LIMIT', 20) +output_file = "tmp/#{filename}.dump" + +StackProf.run(mode: :wall, out: output_file, interval: interval) do + RSpec::Core::Runner.run(ARGV, $stderr, $stdout) +end + +system("stackprof #{output_file} --text --limit #{limit}") diff --git a/changelogs/unreleased/fix-ca-no-date.yml b/changelogs/unreleased/fix-ca-no-date.yml new file mode 100644 index 00000000000..6de4a56ac0d --- /dev/null +++ b/changelogs/unreleased/fix-ca-no-date.yml @@ -0,0 +1,4 @@ +--- +title: Fix for error thrown in cycle analytics events if build has not started +merge_request: +author: diff --git a/changelogs/unreleased/fix-create-pipeline-with-builds-in-transaction.yml b/changelogs/unreleased/fix-create-pipeline-with-builds-in-transaction.yml new file mode 100644 index 00000000000..e37841e80c3 --- /dev/null +++ b/changelogs/unreleased/fix-create-pipeline-with-builds-in-transaction.yml @@ -0,0 +1,4 @@ +--- +title: Create builds in transaction to avoid empty pipelines +merge_request: 7742 +author: diff --git a/changelogs/unreleased/sh-update-sidekiq-cron.yml b/changelogs/unreleased/sh-update-sidekiq-cron.yml new file mode 100644 index 00000000000..d79ba817a18 --- /dev/null +++ b/changelogs/unreleased/sh-update-sidekiq-cron.yml @@ -0,0 +1,4 @@ +--- +title: Update Sidekiq-cron to fix compatibility issues with Sidekiq 4.2.1 +merge_request: +author: diff --git a/doc/development/performance.md b/doc/development/performance.md index 8337c2d9cb3..5c43ae7b79a 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -101,6 +101,116 @@ In short: 5. If you must write a benchmark use the benchmark-ips Gem instead of Ruby's `Benchmark` module. +## Profiling + +By collecting snapshots of process state at regular intervals, profiling allows +you to see where time is spent in a process. The [StackProf](https://github.com/tmm1/stackprof) +gem is included in GitLab's development environment, allowing you to investigate +the behaviour of suspect code in detail. + +It's important to note that profiling an application *alters its performance*, +and will generally be done *in an unrepresentative environment*. In particular, +a method is not necessarily troublesome just because it is executed many times, +or takes a long time to execute. Profiles are tools you can use to better +understand what is happening in an application - using that information wisely +is up to you! + +Keeping that in mind, to create a profile, identify (or create) a spec that +exercises the troublesome code path, then run it using the `bin/rspec-stackprof` +helper, e.g.: + +``` +$ LIMIT=10 bin/rspec-stackprof spec/policies/project_policy_spec.rb +8/8 |====== 100 ======>| Time: 00:00:18 + +Finished in 18.19 seconds (files took 4.8 seconds to load) +8 examples, 0 failures + +================================== + Mode: wall(1000) + Samples: 17033 (5.59% miss rate) + GC: 1901 (11.16%) +================================== + TOTAL (pct) SAMPLES (pct) FRAME + 6000 (35.2%) 2566 (15.1%) Sprockets::Cache::FileStore#get + 2018 (11.8%) 888 (5.2%) ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_no_cache + 1338 (7.9%) 640 (3.8%) ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements#execute + 3125 (18.3%) 394 (2.3%) Sprockets::Cache::FileStore#safe_open + 913 (5.4%) 301 (1.8%) ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_cache + 288 (1.7%) 288 (1.7%) ActiveRecord::Attribute#initialize + 246 (1.4%) 246 (1.4%) Sprockets::Cache::FileStore#safe_stat + 295 (1.7%) 193 (1.1%) block (2 levels) in class_attribute + 187 (1.1%) 187 (1.1%) block (4 levels) in class_attribute +``` + +You can limit the specs that are run by passing any arguments `rspec` would +normally take. + +The output is sorted by the `Samples` column by default. This is the number of +samples taken where the method is the one currently being executed. The `Total` +column shows the number of samples taken where the method, or any of the methods +it calls, were being executed. + +To create a graphical view of the call stack: + +```shell +$ stackprof tmp/project_policy_spec.rb.dump --graphviz > project_policy_spec.dot +$ dot -Tsvg project_policy_spec.dot > project_policy_spec.svg +``` + +To load the profile in [kcachegrind](https://kcachegrind.github.io/): + +``` +$ stackprof tmp/project_policy_spec.dump --callgrind > project_policy_spec.callgrind +$ kcachegrind project_policy_spec.callgrind # Linux +$ qcachegrind project_policy_spec.callgrind # Mac +``` + +It may be useful to zoom in on a specific method, e.g.: + +``` +$ stackprof tmp/project_policy_spec.rb.dump --method warm_asset_cache +TestEnv#warm_asset_cache (/Users/lupine/dev/gitlab.com/gitlab-org/gitlab-development-kit/gitlab/spec/support/test_env.rb:164) + samples: 0 self (0.0%) / 6288 total (36.9%) + callers: + 6288 ( 100.0%) block (2 levels) in <top (required)> + callees (6288 total): + 6288 ( 100.0%) Capybara::RackTest::Driver#visit + code: + | 164 | def warm_asset_cache + | 165 | return if warm_asset_cache? + | 166 | return unless defined?(Capybara) + | 167 | + 6288 (36.9%) | 168 | Capybara.current_session.driver.visit '/' + | 169 | end +$ stackprof tmp/project_policy_spec.rb.dump --method BasePolicy#abilities +BasePolicy#abilities (/Users/lupine/dev/gitlab.com/gitlab-org/gitlab-development-kit/gitlab/app/policies/base_policy.rb:79) + samples: 0 self (0.0%) / 50 total (0.3%) + callers: + 25 ( 50.0%) BasePolicy.abilities + 25 ( 50.0%) BasePolicy#collect_rules + callees (50 total): + 25 ( 50.0%) ProjectPolicy#rules + 25 ( 50.0%) BasePolicy#collect_rules + code: + | 79 | def abilities + | 80 | return RuleSet.empty if @user && @user.blocked? + | 81 | return anonymous_abilities if @user.nil? + 50 (0.3%) | 82 | collect_rules { rules } + | 83 | end +``` + +Since the profile includes the work done by the test suite as well as the +application code, these profiles can be used to investigate slow tests as well. +However, for smaller runs (like this example), this means that the cost of +setting up the test suite will tend to dominate. + +It's also possible to modify the application code in-place to output profiles +whenever a particular code path is triggered without going through the test +suite first. See the +[StackProf documentation](https://github.com/tmm1/stackprof/blob/master/README.md) +for details. + ## Importance of Changes When working on performance improvements, it's important to always ask yourself diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 90fa588b455..97baebc1d27 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -192,7 +192,7 @@ module API should_remove_source_branch: params[:should_remove_source_branch] } - if params[:merge_when_build_succeeds] && merge_request.pipeline && merge_request.pipeline.active? + if params[:merge_when_build_succeeds] && merge_request.head_pipeline && merge_request.head_pipeline.active? ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params). execute(merge_request) else diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb index ac2a1ba5dff..1735791f644 100644 --- a/spec/factories/ci/pipelines.rb +++ b/spec/factories/ci/pipelines.rb @@ -7,26 +7,30 @@ FactoryGirl.define do project factory: :empty_project factory :ci_pipeline_without_jobs do - after(:build) do |commit| - allow(commit).to receive(:ci_yaml_file) { YAML.dump({}) } + after(:build) do |pipeline| + allow(pipeline).to receive(:ci_yaml_file) { YAML.dump({}) } end end factory :ci_pipeline_with_one_job do - after(:build) do |commit| - allow(commit).to receive(:ci_yaml_file) { YAML.dump({ rspec: { script: "ls" } }) } - end - end - - factory :ci_pipeline_with_two_job do - after(:build) do |commit| - allow(commit).to receive(:ci_yaml_file) { YAML.dump({ rspec: { script: "ls" }, spinach: { script: "ls" } }) } + after(:build) do |pipeline| + allow(pipeline).to receive(:ci_yaml_file) do + YAML.dump({ rspec: { script: "ls" } }) + end end end factory :ci_pipeline do - after(:build) do |commit| - allow(commit).to receive(:ci_yaml_file) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) } + transient { config nil } + + after(:build) do |pipeline, evaluator| + allow(pipeline).to receive(:ci_yaml_file) do + if evaluator.config + YAML.dump(evaluator.config) + else + File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + end + end end end end diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb index 35fcef7a712..87cce32d6c6 100644 --- a/spec/features/security/group/internal_access_spec.rb +++ b/spec/features/security/group/internal_access_spec.rb @@ -3,25 +3,12 @@ require 'rails_helper' describe 'Internal Group access', feature: true do include AccessMatchers - let(:group) { create(:group, :internal) } + let(:group) { create(:group, :internal) } let(:project) { create(:project, :internal, group: group) } - - let(:owner) { create(:user) } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - let(:project_guest) { create(:user) } - - before do - group.add_owner(owner) - group.add_master(master) - group.add_developer(developer) - group.add_reporter(reporter) - group.add_guest(guest) - - project.team << [project_guest, :guest] + let(:project_guest) do + create(:user) do |user| + project.add_guest(user) + end end describe "Group should be internal" do @@ -34,75 +21,75 @@ describe 'Internal Group access', feature: true do describe 'GET /groups/:path' do subject { group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/issues' do subject { issues_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/merge_requests' do subject { merge_requests_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/edit' do subject { edit_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_denied_for(:master).of(group) } + it { is_expected.to be_denied_for(:developer).of(group) } + it { is_expected.to be_denied_for(:reporter).of(group) } + it { is_expected.to be_denied_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end end diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb index 75a93342628..1d6b3e77c22 100644 --- a/spec/features/security/group/private_access_spec.rb +++ b/spec/features/security/group/private_access_spec.rb @@ -3,25 +3,12 @@ require 'rails_helper' describe 'Private Group access', feature: true do include AccessMatchers - let(:group) { create(:group, :private) } + let(:group) { create(:group, :private) } let(:project) { create(:project, :private, group: group) } - - let(:owner) { create(:user) } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - let(:project_guest) { create(:user) } - - before do - group.add_owner(owner) - group.add_master(master) - group.add_developer(developer) - group.add_reporter(reporter) - group.add_guest(guest) - - project.team << [project_guest, :guest] + let(:project_guest) do + create(:user) do |user| + project.add_guest(user) + end end describe "Group should be private" do @@ -34,75 +21,75 @@ describe 'Private Group access', feature: true do describe 'GET /groups/:path' do subject { group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/issues' do subject { issues_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/merge_requests' do subject { merge_requests_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/edit' do subject { edit_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_denied_for(:master).of(group) } + it { is_expected.to be_denied_for(:developer).of(group) } + it { is_expected.to be_denied_for(:reporter).of(group) } + it { is_expected.to be_denied_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end end diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb index 6c5ee93970b..d7d76177269 100644 --- a/spec/features/security/group/public_access_spec.rb +++ b/spec/features/security/group/public_access_spec.rb @@ -3,25 +3,12 @@ require 'rails_helper' describe 'Public Group access', feature: true do include AccessMatchers - let(:group) { create(:group, :public) } + let(:group) { create(:group, :public) } let(:project) { create(:project, :public, group: group) } - - let(:owner) { create(:user) } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - let(:project_guest) { create(:user) } - - before do - group.add_owner(owner) - group.add_master(master) - group.add_developer(developer) - group.add_reporter(reporter) - group.add_guest(guest) - - project.team << [project_guest, :guest] + let(:project_guest) do + create(:user) do |user| + project.add_guest(user) + end end describe "Group should be public" do @@ -34,75 +21,75 @@ describe 'Public Group access', feature: true do describe 'GET /groups/:path' do subject { group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/issues' do subject { issues_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/merge_requests' do subject { merge_requests_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/edit' do subject { edit_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_denied_for(:master).of(group) } + it { is_expected.to be_denied_for(:developer).of(group) } + it { is_expected.to be_denied_for(:reporter).of(group) } + it { is_expected.to be_denied_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 58ccd056328..26034cb1c7b 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -570,7 +570,7 @@ describe MergeRequest, models: true do end end - describe '#pipeline' do + describe '#head_pipeline' do describe 'when the source project exists' do it 'returns the latest pipeline' do pipeline = double(:ci_pipeline, ref: 'master') @@ -581,7 +581,7 @@ describe MergeRequest, models: true do with('master', '123abc'). and_return(pipeline) - expect(subject.pipeline).to eq(pipeline) + expect(subject.head_pipeline).to eq(pipeline) end end @@ -589,7 +589,7 @@ describe MergeRequest, models: true do it 'returns nil' do allow(subject).to receive(:source_project).and_return(nil) - expect(subject.pipeline).to be_nil + expect(subject.head_pipeline).to be_nil end end end @@ -857,7 +857,7 @@ describe MergeRequest, models: true do context 'and a failed pipeline is associated' do before do pipeline.update(status: 'failed') - allow(subject).to receive(:pipeline) { pipeline } + allow(subject).to receive(:head_pipeline) { pipeline } end it { expect(subject.mergeable_ci_state?).to be_falsey } @@ -866,7 +866,7 @@ describe MergeRequest, models: true do context 'and a successful pipeline is associated' do before do pipeline.update(status: 'success') - allow(subject).to receive(:pipeline) { pipeline } + allow(subject).to receive(:head_pipeline) { pipeline } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -875,7 +875,7 @@ describe MergeRequest, models: true do context 'and a skipped pipeline is associated' do before do pipeline.update(status: 'skipped') - allow(subject).to receive(:pipeline) { pipeline } + allow(subject).to receive(:head_pipeline) { pipeline } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -883,7 +883,7 @@ describe MergeRequest, models: true do context 'when no pipeline is associated' do before do - allow(subject).to receive(:pipeline) { nil } + allow(subject).to receive(:head_pipeline) { nil } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -896,7 +896,7 @@ describe MergeRequest, models: true do context 'and a failed pipeline is associated' do before do pipeline.statuses << create(:commit_status, status: 'failed', project: project) - allow(subject).to receive(:pipeline) { pipeline } + allow(subject).to receive(:head_pipeline) { pipeline } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -904,7 +904,7 @@ describe MergeRequest, models: true do context 'when no pipeline is associated' do before do - allow(subject).to receive(:pipeline) { nil } + allow(subject).to receive(:head_pipeline) { nil } end it { expect(subject.mergeable_ci_state?).to be_truthy } diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 3ecf3eea5f5..edc985b765b 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -466,7 +466,7 @@ describe API::API, api: true do end it "enables merge when build succeeds if the ci is active" do - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) + allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline) allow(pipeline).to receive(:active?).and_return(true) put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_build_succeeds: true diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb index 705dbb7d1c0..5c90fd9bad9 100644 --- a/spec/requests/projects/cycle_analytics_events_spec.rb +++ b/spec/requests/projects/cycle_analytics_events_spec.rb @@ -40,7 +40,7 @@ describe 'cycle analytics events' do expect(json_response['events']).not_to be_empty - first_mr_iid = MergeRequest.order(created_at: :desc).pluck(:iid).first.to_s + first_mr_iid = project.merge_requests.order(id: :desc).pluck(:iid).first.to_s expect(json_response['events'].first['iid']).to eq(first_mr_iid) end diff --git a/spec/serializers/analytics_build_entity_spec.rb b/spec/serializers/analytics_build_entity_spec.rb index c0b7e86b17c..6b33fe66a63 100644 --- a/spec/serializers/analytics_build_entity_spec.rb +++ b/spec/serializers/analytics_build_entity_spec.rb @@ -7,7 +7,9 @@ describe AnalyticsBuildEntity do context 'build with an author' do let(:user) { create(:user) } - let(:build) { create(:ci_build, author: user, started_at: 2.hours.ago, finished_at: 1.hour.ago) } + let(:started_at) { 2.hours.ago } + let(:finished_at) { 1.hour.ago } + let(:build) { create(:ci_build, author: user, started_at: started_at, finished_at: finished_at) } subject { entity.as_json } @@ -31,5 +33,54 @@ describe AnalyticsBuildEntity do it 'contains the duration' do expect(subject[:total_time]).to eq(hours: 1 ) end + + context 'no started at or finished at date' do + let(:started_at) { nil } + let(:finished_at) { nil } + + it 'does not blow up' do + expect{ subject[:date] }.not_to raise_error + end + + it 'shows the right message' do + expect(subject[:date]).to eq('Not started') + end + + it 'shows the right total time' do + expect(subject[:total_time]).to eq({}) + end + end + + context 'no started at date' do + let(:started_at) { nil } + + it 'does not blow up' do + expect{ subject[:date] }.not_to raise_error + end + + it 'shows the right message' do + expect(subject[:date]).to eq('Not started') + end + + it 'shows the right total time' do + expect(subject[:total_time]).to eq({}) + end + end + + context 'no finished at date' do + let(:finished_at) { nil } + + it 'does not blow up' do + expect{ subject[:date] }.not_to raise_error + end + + it 'shows the right message' do + expect(subject[:date]).to eq('about 2 hours ago') + end + + it 'shows the right total time' do + expect(subject[:total_time]).to eq({ hours: 2 }) + end + end end end diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index ff113efd916..ebb11166964 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -1,31 +1,10 @@ require 'spec_helper' describe Ci::ProcessPipelineService, services: true do - let(:pipeline) { create(:ci_pipeline, ref: 'master') } + let(:pipeline) { create(:ci_empty_pipeline, ref: 'master') } let(:user) { create(:user) } - let(:config) { nil } - - before do - allow(pipeline).to receive(:ci_yaml_file).and_return(config) - end describe '#execute' do - def all_builds - pipeline.builds - end - - def builds - all_builds.where.not(status: [:created, :skipped]) - end - - def process_pipeline - described_class.new(pipeline.project, user).execute(pipeline) - end - - def succeed_pending - builds.pending.update_all(status: 'success') - end - context 'start queuing next builds' do before do create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage_idx: 0) @@ -223,10 +202,6 @@ describe Ci::ProcessPipelineService, services: true do pipeline.builds.running_or_pending.each(&:success) expect(manual_actions).to be_many # production and clear cache end - - def manual_actions - pipeline.manual_actions - end end end @@ -282,15 +257,6 @@ describe Ci::ProcessPipelineService, services: true do expect(builds.map(&:status)).to eq(%w[success skipped pending]) end end - - def create_build(name, stage_idx, when_value = nil) - create(:ci_build, - :created, - pipeline: pipeline, - name: name, - stage_idx: stage_idx, - when: when_value) - end end context 'when failed build in the middle stage is retried' do @@ -327,65 +293,92 @@ describe Ci::ProcessPipelineService, services: true do end end - context 'creates a builds from .gitlab-ci.yml' do - let(:config) do - YAML.dump({ - rspec: { - stage: 'test', - script: 'rspec' - }, - rubocop: { - stage: 'test', - script: 'rubocop' - }, - deploy: { - stage: 'deploy', - script: 'deploy' - } - }) + context 'when there are builds that are not created yet' do + let(:pipeline) do + create(:ci_pipeline, config: config) end - # Using stubbed .gitlab-ci.yml created in commit factory - # + let(:config) do + { rspec: { stage: 'test', script: 'rspec' }, + deploy: { stage: 'deploy', script: 'rsync' } } + end before do - stub_ci_pipeline_yaml_file(config) create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage: 'build', stage_idx: 0) create(:ci_build, :created, pipeline: pipeline, name: 'mac', stage: 'build', stage_idx: 0) end - it 'when processing a pipeline' do - # Currently we have two builds with state created + it 'processes the pipeline' do + # Currently we have five builds with state created + # expect(builds.count).to eq(0) expect(all_builds.count).to eq(2) - # Create builds will mark the created as pending - expect(process_pipeline).to be_truthy + # Process builds service will enqueue builds from the first stage. + # + process_pipeline + expect(builds.count).to eq(2) expect(all_builds.count).to eq(2) - # When we builds succeed we will create a rest of pipeline from .gitlab-ci.yml - # We will have 2 succeeded, 2 pending (from stage test), total 5 (one more build from deploy) + # When builds succeed we will enqueue remaining builds. + # + # We will have 2 succeeded, 1 pending (from stage test), total 4 (two + # additional build from `.gitlab-ci.yml`). + # succeed_pending - expect(process_pipeline).to be_truthy + process_pipeline + expect(builds.success.count).to eq(2) - expect(builds.pending.count).to eq(2) - expect(all_builds.count).to eq(5) + expect(builds.pending.count).to eq(1) + expect(all_builds.count).to eq(4) - # When we succeed the 2 pending from stage test, - # We will queue a deploy stage, no new builds will be created + # When pending build succeeds in stage test, we enqueue deploy stage. + # succeed_pending - expect(process_pipeline).to be_truthy + process_pipeline + expect(builds.pending.count).to eq(1) - expect(builds.success.count).to eq(4) - expect(all_builds.count).to eq(5) + expect(builds.success.count).to eq(3) + expect(all_builds.count).to eq(4) - # When we succeed last pending build, we will have a total of 5 succeeded builds, no new builds will be created + # When the last one succeeds we have 4 successful builds. + # succeed_pending - expect(process_pipeline).to be_falsey - expect(builds.success.count).to eq(5) - expect(all_builds.count).to eq(5) + process_pipeline + + expect(builds.success.count).to eq(4) + expect(all_builds.count).to eq(4) end end end + + def all_builds + pipeline.builds + end + + def builds + all_builds.where.not(status: [:created, :skipped]) + end + + def process_pipeline + described_class.new(pipeline.project, user).execute(pipeline) + end + + def succeed_pending + builds.pending.update_all(status: 'success') + end + + def manual_actions + pipeline.manual_actions + end + + def create_build(name, stage_idx, when_value = nil) + create(:ci_build, + :created, + pipeline: pipeline, + name: name, + stage_idx: stage_idx, + when: when_value) + end end diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb index a44312dd363..bb7830c7eea 100644 --- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb +++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb @@ -20,13 +20,19 @@ describe MergeRequests::AddTodoWhenBuildFailsService do let(:todo_service) { TodoService.new } let(:merge_request) do - create(:merge_request, merge_user: user, source_branch: 'master', - target_branch: 'feature', source_project: project, target_project: project, + create(:merge_request, merge_user: user, + source_branch: 'master', + target_branch: 'feature', + source_project: project, + target_project: project, state: 'opened') end before do - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) + allow_any_instance_of(MergeRequest) + .to receive(:head_pipeline) + .and_return(pipeline) + allow(service).to receive(:todo_service).and_return(todo_service) end diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index c0164138713..963d9573ac4 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -21,7 +21,10 @@ describe MergeRequests::MergeWhenBuildSucceedsService do context 'first time enabling' do before do - allow(merge_request).to receive(:pipeline).and_return(pipeline) + allow(merge_request) + .to receive(:head_pipeline) + .and_return(pipeline) + service.execute(merge_request) end @@ -43,8 +46,12 @@ describe MergeRequests::MergeWhenBuildSucceedsService do let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) } before do - allow(mr_merge_if_green_enabled).to receive(:pipeline).and_return(pipeline) - allow(mr_merge_if_green_enabled).to receive(:mergeable?).and_return(true) + allow(mr_merge_if_green_enabled).to receive(:head_pipeline) + .and_return(pipeline) + + allow(mr_merge_if_green_enabled).to receive(:mergeable?) + .and_return(true) + allow(pipeline).to receive(:success?).and_return(true) end @@ -138,9 +145,12 @@ describe MergeRequests::MergeWhenBuildSucceedsService do before do # This behavior of MergeRequest: we instantiate a new object - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_wrap_original do - Ci::Pipeline.find(pipeline.id) - end + # + allow_any_instance_of(MergeRequest) + .to receive(:head_pipeline) + .and_wrap_original do + Ci::Pipeline.find(pipeline.id) + end end it "doesn't merge if any of stages failed" do diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb index 691d7e05f57..ceddb656596 100644 --- a/spec/support/matchers/access_matchers.rb +++ b/spec/support/matchers/access_matchers.rb @@ -7,7 +7,7 @@ module AccessMatchers extend RSpec::Matchers::DSL include Warden::Test::Helpers - def emulate_user(user, project = nil) + def emulate_user(user, membership = nil) case user when :user login_as(create(:user)) @@ -19,16 +19,17 @@ module AccessMatchers login_as(create(:user, external: true)) when User login_as(user) - when :owner - raise ArgumentError, "cannot emulate owner without project" unless project - - login_as(project.owner) - when *Gitlab::Access.sym_options.keys - raise ArgumentError, "cannot emulate user #{user} without project" unless project + when *Gitlab::Access.sym_options_with_owner.keys + raise ArgumentError, "cannot emulate #{user} without membership parent" unless membership role = user - user = create(:user) - project.public_send(:"add_#{role}", user) + + if role == :owner && membership.owner + user = membership.owner + else + user = create(:user) + membership.public_send(:"add_#{role}", user) + end login_as(user) else @@ -47,14 +48,14 @@ module AccessMatchers matcher :be_allowed_for do |user| match do |url| - emulate_user(user, @project) + emulate_user(user, @membership) visit(url) status_code != 404 && current_path != new_user_session_path end - chain :of do |project| - @project = project + chain :of do |membership| + @membership = membership end description { description_for(user, 'allowed') } @@ -62,14 +63,14 @@ module AccessMatchers matcher :be_denied_for do |user| match do |url| - emulate_user(user, @project) + emulate_user(user, @membership) visit(url) status_code == 404 || current_path == new_user_session_path end - chain :of do |project| - @project = project + chain :of do |membership| + @membership = membership end description { description_for(user, 'denied') } |