Commit 3eef0e18 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'refactor-build-service' into 'master'

Refactor Ci::Commit and Ci::Build to have all builds for same :sha on single page

This makes Ci::Commit to have only :sha and simplifies routing to have only :sha in path. The :ref and :push_data is now parameter of Ci::Build.

All commit related data (git author, message and .gitlab-ci.yml) is read directly from repository.

All code related for creating builds is moved to CreateBuildsService.

Status deduction is rewritten to make if more efficient and easier to integrate with Commit Status API.

This is partially working, tests are not yet touched.

This slightly changes view of Commit:
![Screen_Shot_2015-10-02_at_15.21.47](https://gitlab.com/gitlab-org/gitlab-ce/uploads/ad3f1ccdcc87659ea437d8db6c5b9f94/Screen_Shot_2015-10-02_at_15.21.47.png)

@dzaporozhets What do you think?


See merge request !1502
parents 97f7edf3 97a11136
......@@ -18,7 +18,7 @@ module Ci
if commit
# Redirect to commit page
redirect_to ci_project_ref_commit_path(@project, @build.commit.ref, @build.commit.sha)
redirect_to ci_project_commit_path(@project, @build.commit)
return
end
end
......
......@@ -13,7 +13,7 @@ module Ci
end
def status
commit = Ci::Project.find(params[:project_id]).commits.find_by_sha_and_ref!(params[:id], params[:ref_id])
commit = Ci::Project.find(params[:project_id]).commits.find_by_sha!(params[:id])
render json: commit.to_json(only: [:id, :sha], methods: [:status, :coverage])
rescue ActiveRecord::RecordNotFound
render json: { status: "not_found" }
......@@ -22,7 +22,7 @@ module Ci
def cancel
commit.builds.running_or_pending.each(&:cancel)
redirect_to ci_project_ref_commits_path(project, commit.ref, commit.sha)
redirect_to ci_project_commits_path(project, commit.sha)
end
private
......@@ -32,7 +32,7 @@ module Ci
end
def commit
@commit ||= Ci::Project.find(params[:project_id]).commits.find_by_sha_and_ref!(params[:id], params[:ref_id])
@commit ||= Ci::Project.find(params[:project_id]).commits.find_by_sha!(params[:id])
end
end
end
......@@ -19,7 +19,8 @@ module Ci
@ref = params[:ref]
@commits = @project.commits.reverse_order
@commits = @commits.where(ref: @ref) if @ref
# TODO: this is broken
# @commits = @commits.where(ref: @ref) if @ref
@commits = @commits.page(params[:page]).per(20)
end
......
......@@ -3,10 +3,6 @@ module BuildsHelper
gitlab_ref_link build.project, build.ref
end
def build_compare_link build
gitlab_compare_link build.project, build.commit.short_before_sha, build.short_sha
end
def build_commit_link build
gitlab_commit_link build.project, build.short_sha
end
......
module Ci
module CommitsHelper
def ci_commit_path(commit)
ci_project_ref_commits_path(commit.project, commit.ref, commit.sha)
ci_project_commits_path(commit.project, commit)
end
def commit_link(commit)
......
......@@ -26,7 +26,7 @@ module Ci
def yaml_web_editor_link(project)
commits = project.commits
if commits.any? && commits.last.push_data[:ci_yaml_file]
if commits.any? && commits.last.ci_yaml_file
"#{project.gitlab_url}/edit/master/.gitlab-ci.yml"
else
"#{project.gitlab_url}/new/master"
......
module CiStatusHelper
def ci_status_path(ci_commit)
ci_project_ref_commits_path(ci_commit.project, ci_commit.ref, ci_commit)
ci_project_commits_path(ci_commit.project, ci_commit)
end
def ci_status_icon(ci_commit)
......
......@@ -32,12 +32,14 @@ module Ci
belongs_to :commit, class_name: 'Ci::Commit'
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
belongs_to :user
serialize :options
validates :commit, presence: true
validates :status, presence: true
validates :coverage, numericality: true, allow_blank: true
validates_presence_of :ref
scope :running, ->() { where(status: "running") }
scope :pending, ->() { where(status: "pending") }
......@@ -45,6 +47,10 @@ module Ci
scope :failed, ->() { where(status: "failed") }
scope :unstarted, ->() { where(runner_id: nil) }
scope :running_or_pending, ->() { where(status:[:running, :pending]) }
scope :latest, ->() { where(id: unscope(:select).select('max(id)').group(:name)).order(stage_idx: :asc) }
scope :ignore_failures, ->() { where(allow_failure: false) }
scope :for_ref, ->(ref) { where(ref: ref) }
scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
acts_as_taggable
......@@ -75,6 +81,8 @@ module Ci
def retry(build)
new_build = Ci::Build.new(status: :pending)
new_build.ref = build.ref
new_build.tag = build.tag
new_build.options = build.options
new_build.commands = build.commands
new_build.tag_list = build.tag_list
......@@ -82,6 +90,7 @@ module Ci
new_build.name = build.name
new_build.allow_failure = build.allow_failure
new_build.stage = build.stage
new_build.stage_idx = build.stage_idx
new_build.trigger_request = build.trigger_request
new_build.save
new_build
......@@ -117,8 +126,8 @@ module Ci
Ci::WebHookService.new.build_end(build)
end
if build.commit.success?
build.commit.create_next_builds(build.trigger_request)
if build.commit.should_create_next_builds?(build)
build.commit.create_next_builds(build.ref, build.tag, build.user, build.trigger_request)
end
project.execute_services(build)
......@@ -135,9 +144,13 @@ module Ci
state :canceled, value: 'canceled'
end
delegate :sha, :short_sha, :before_sha, :ref, :project,
delegate :sha, :short_sha, :project,
to: :commit, prefix: false
def before_sha
Gitlab::Git::BLANK_SHA
end
def trace_html
html = Ci::Ansi2html::convert(trace) if trace.present?
html || ''
......@@ -187,6 +200,16 @@ module Ci
project.name
end
def project_recipients
recipients = project.email_recipients.split(' ')
if project.email_add_pusher? && user.present? && user.notification_email.present?
recipients << user.notification_email
end
recipients.uniq
end
def repo_url
project.repo_url_with_auth
end
......
......@@ -23,9 +23,7 @@ module Ci
has_many :builds, dependent: :destroy, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
serialize :push_data
validates_presence_of :ref, :sha, :before_sha, :push_data
validates_presence_of :sha
validate :valid_commit_sha
def self.truncate_sha(sha)
......@@ -60,28 +58,16 @@ module Ci
end
end
def new_branch?
before_sha == Ci::Git::BLANK_SHA
end
def compare?
!new_branch?
end
def git_author_name
commit_data[:author][:name] if commit_data && commit_data[:author]
commit_data.author_name if commit_data
end
def git_author_email
commit_data[:author][:email] if commit_data && commit_data[:author]
commit_data.author_email if commit_data
end
def git_commit_message
commit_data[:message] if commit_data && commit_data[:message]
end
def short_before_sha
Ci::Commit.truncate_sha(before_sha)
commit_data.message if commit_data
end
def short_sha
......@@ -89,84 +75,51 @@ module Ci
end
def commit_data
push_data[:commits].find do |commit|
commit[:id] == sha
end
@commit ||= gl_project.commit(sha)
rescue
nil
end
def project_recipients
recipients = project.email_recipients.split(' ')
if project.email_add_pusher? && push_data[:user_email].present?
recipients << push_data[:user_email]
end
recipients.uniq
end
def stage
return unless config_processor
stages = builds_without_retry.select(&:active?).map(&:stage)
config_processor.stages.find { |stage| stages.include? stage }
running_or_pending = builds_without_retry.running_or_pending
running_or_pending.limit(1).pluck(:stage).first
end
def create_builds_for_stage(stage, trigger_request)
def create_builds(ref, tag, user, trigger_request = nil)
return if skip_ci? && trigger_request.blank?
return unless config_processor
builds_attrs = config_processor.builds_for_stage_and_ref(stage, ref, tag)
builds_attrs.map do |build_attrs|
builds.create!({
name: build_attrs[:name],
commands: build_attrs[:script],
tag_list: build_attrs[:tags],
options: build_attrs[:options],
allow_failure: build_attrs[:allow_failure],
stage: build_attrs[:stage],
trigger_request: trigger_request,
})
config_processor.stages.any? do |stage|
CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present?
end
end
def create_next_builds(trigger_request)
def create_next_builds(ref, tag, user, trigger_request)
return if skip_ci? && trigger_request.blank?
return unless config_processor
stages = builds.where(trigger_request: trigger_request).group_by(&:stage)
stages = builds.where(ref: ref, tag: tag, trigger_request: trigger_request).group_by(&:stage)
config_processor.stages.any? do |stage|
!stages.include?(stage) && create_builds_for_stage(stage, trigger_request).present?
unless stages.include?(stage)
CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present?
end
end
end
def create_builds(trigger_request = nil)
return if skip_ci? && trigger_request.blank?
return unless config_processor
config_processor.stages.any? do |stage|
create_builds_for_stage(stage, trigger_request).present?
def refs
builds.group(:ref).pluck(:ref)
end
def last_ref
builds.latest.first.try(:ref)
end
def builds_without_retry
@builds_without_retry ||=
begin
grouped_builds = builds.group_by(&:name)
grouped_builds.map do |name, builds|
builds.sort_by(&:id).last
end
end
builds.latest
end
def builds_without_retry_sorted
return builds_without_retry unless config_processor
stages = config_processor.stages
builds_without_retry.sort_by do |build|
[stages.index(build.stage) || -1, build.name || ""]
end
def builds_without_retry_for_ref(ref)
builds.for_ref(ref).latest
end
def retried_builds
......@@ -225,6 +178,10 @@ module Ci
@duration ||= builds_without_retry.select(&:duration).sum(&:duration).to_i
end
def duration_for_ref(ref)
builds_without_retry_for_ref(ref).select(&:duration).sum(&:duration).to_i
end
def finished_at
@finished_at ||= builds.order('finished_at DESC').first.try(:finished_at)
end
......@@ -238,12 +195,12 @@ module Ci
end
end
def matrix?
builds_without_retry.size > 1
def matrix_for_ref?(ref)
builds_without_retry_for_ref(ref).pluck(:id).size > 1
end
def config_processor
@config_processor ||= Ci::GitlabCiYamlProcessor.new(push_data[:ci_yaml_file])
@config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file)
rescue Ci::GitlabCiYamlProcessor::ValidationError => e
save_yaml_error(e.message)
nil
......@@ -253,16 +210,31 @@ module Ci
nil
end
def ci_yaml_file
gl_project.repository.blob_at(sha, '.gitlab-ci.yml')
rescue
nil
end
def skip_ci?
return false if builds.any?
commits = push_data[:commits]
commits.present? && commits.last[:message] =~ /(\[ci skip\])/
git_commit_message =~ /(\[ci skip\])/ if git_commit_message
end
def update_committed!
update!(committed_at: DateTime.now)
end
def should_create_next_builds?(build)
# don't create other builds if this one is retried
other_builds = builds.similar(build).latest
return false unless other_builds.include?(build)
other_builds.all? do |build|
build.success? || build.ignored?
end
end
private
def save_yaml_error(error)
......
......@@ -28,18 +28,6 @@ module Ci
status
end
# only check for toggling build status within same ref.
def last_commit_changed_status?
ref = last_commit.ref
last_commits = commits.where(ref: ref).last(2)
if last_commits.size < 2
false
else
last_commits[0].status != last_commits[1].status
end
end
def last_commit_for_ref(ref)
commits.where(ref: ref).last
end
......
......@@ -744,7 +744,11 @@ class Project < ActiveRecord::Base
end
def ci_commit(sha)
gitlab_ci_project.commits.find_by(sha: sha) if gitlab_ci?
ci_commits.find_by(sha: sha)
end
def ensure_ci_commit(sha)
ci_commit(sha) || ci_commits.create(sha: sha)
end
def ensure_gitlab_ci_project
......
......@@ -11,14 +11,7 @@ module Ci
def to_s
lines = Array.new
lines.push("<a href=\"#{ci_project_url(project)}\">#{project.name}</a> - ")
if commit.matrix?
lines.push("<a href=\"#{ci_project_ref_commits_url(project, commit.ref, commit.sha)}\">Commit ##{commit.id}</a></br>")
else
first_build = commit.builds_without_retry.first
lines.push("<a href=\"#{ci_project_build_url(project, first_build)}\">Build '#{first_build.name}' ##{first_build.id}</a></br>")
end
lines.push("<a href=\"#{ci_project_commits_url(project, commit.sha)}\">Commit ##{commit.id}</a></br>")
lines.push("#{commit.short_sha} #{commit.git_author_name} - #{commit.git_commit_message}</br>")
lines.push("#{humanized_status(commit_status)} in #{commit.duration} second(s).")
lines.join('')
......
......@@ -61,7 +61,7 @@ module Ci
end
def execute(build)
build.commit.project_recipients.each do |recipient|
build.project_recipients.each do |recipient|
case build.status.to_sym
when :success
mailer.build_success_email(build.id, recipient)
......
......@@ -23,7 +23,6 @@ module Ci
def attachments
fields = []
if commit.matrix?
commit.builds_without_retry.each do |build|
next if build.allow_failure?
next unless build.failed?
......@@ -32,7 +31,6 @@ module Ci
value: "Build <#{ci_project_build_url(project, build)}|\##{build.id}> failed in #{build.duration.to_i} second(s)."
}
end
end
[{
text: attachment_message,
......@@ -47,12 +45,7 @@ module Ci
def attachment_message
out = "<#{ci_project_url(project)}|#{project_name}>: "
if commit.matrix?
out << "Commit <#{ci_project_ref_commits_url(project, commit.ref, commit.sha)}|\##{commit.id}> "
else
build = commit.builds_without_retry.first
out << "Build <#{ci_project_build_url(project, build)}|\##{build.id}> "
end
out << "Commit <#{ci_project_commits_url(project, commit.sha)}|\##{commit.id}> "
out << "(<#{commit_sha_link}|#{commit.short_sha}>) "
out << "of <#{commit_ref_link}|#{commit.ref}> "
out << "by #{commit.git_author_name} " if commit.git_author_name
......
......@@ -40,19 +40,10 @@ class GitlabCiService < CiService
def execute(data)
return unless supported_events.include?(data[:object_kind])
sha = data[:checkout_sha]
if sha.present?
file = ci_yaml_file(sha)
if file && file.data
data.merge!(ci_yaml_file: file.data)
end
end
ci_project = Ci::Project.find_by(gitlab_id: project.id)
ci_project = project.gitlab_ci_project
if ci_project
Ci::CreateCommitService.new.execute(ci_project, data)
current_user = User.find_by(id: data[:user_id])
Ci::CreateCommitService.new.execute(ci_project, current_user, data)
end
end
......@@ -63,7 +54,7 @@ class GitlabCiService < CiService
end
def get_ci_commit(sha, ref)
Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha_and_ref!(sha, ref)
Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha!(sha)
end
def commit_status(sha, ref)
......@@ -80,7 +71,7 @@ class GitlabCiService < CiService
def build_page(sha, ref)
if project.gitlab_ci_project.present?
ci_project_ref_commits_url(project.gitlab_ci_project, ref, sha)
ci_project_commits_url(project.gitlab_ci_project, sha)
end
end
......@@ -99,14 +90,4 @@ class GitlabCiService < CiService
def fields
[]
end
private
def ci_yaml_file(sha)
repository.blob_at(sha, '.gitlab-ci.yml')
end
def repository
project.repository
end
end
......@@ -130,6 +130,7 @@ class User < ActiveRecord::Base
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
has_one :abuse_report, dependent: :destroy
has_many :ci_builds, dependent: :nullify, class_name: 'Ci::Build'
#
......
module Ci
class CreateBuildsService
def execute(commit, stage, ref, tag, user, trigger_request)
builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag)
builds_attrs.map do |build_attrs|
# don't create the same build twice
unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name])
build_attrs.slice!(:name,
:commands,
:tag_list,
:options,
:allow_failure,
:stage,
:stage_idx)
build_attrs.merge!(ref: ref,
tag: tag,
trigger_request: trigger_request,
user: user)
commit.builds.create!(build_attrs)
end
end
end
end
end
module Ci
class CreateCommitService
def execute(project, params)
before_sha = params[:before]
def execute(project, user, params)
sha = params[:checkout_sha] || params[:after]
origin_ref = params[:ref]
......@@ -16,33 +15,10 @@ module Ci
return false
end
commit = project.commits.find_by_sha_and_ref(sha, ref)
# Create commit if not exists yet
unless commit
data = {
ref: ref,
sha: sha,
tag: origin_ref.start_with?('refs/tags/'),
before_sha: before_sha,
push_data: {
before: before_sha,
after: sha,
ref: ref,
user_name: params[:user_name],
user_email: params[:user_email],
repository: params[:repository],
commits: params[:commits],
total_commits_count: params[:total_commits_count],
ci_yaml_file: params[:ci_yaml_file]
}
}
commit = project.commits.create(data)
end
tag = origin_ref.start_with?('refs/tags/')
commit = project.gl_project.ensure_ci_commit(sha)
commit.update_committed!
commit.create_builds unless commit.builds.any?
commit.create_builds(ref, tag, user)
commit
end
......
module Ci
class CreateTriggerRequestService
def execute(project, trigger, ref, variables = nil)
commit = project.commits.where(ref: ref).last
commit = project.gl_project.commit(ref)
return unless commit
# check if ref is tag
tag = project.gl_project.repository.find_tag(ref).present?
ci_commit = project.gl_project.ensure_ci_commit(commit.sha)
trigger_request = trigger.trigger_requests.create!(
commit: commit,
variables: variables
variables: variables,
commit: ci_commit,
)
if commit.create_builds(trigger_request)
if ci_commit.create_builds(ref, tag, nil, trigger_request)
trigger_request
end
end
......
......@@ -27,9 +27,8 @@ module Ci
project_name: project.name,
gitlab_url: project.gitlab_url,
ref: build.ref,
sha: build.sha,
before_sha: build.before_sha,
push_data: build.commit.push_data
sha: build.sha,
})
end
end
......
......@@ -6,6 +6,10 @@
= link_to ci_project_build_path(build.project, build) do
%strong Build ##{build.id}
- if defined?(ref)
%td
= build.ref
%td
= build.stage
......
#up-build-trace
- if @commit.matrix?
- if @commit.matrix_for_ref?(@build.ref)
%ul.center-top-menu
- @commit.builds_without_retry_sorted.each do |build|
- @commit.builds_without_retry_for_ref(build.ref).each do |build|
%li{class: ('active' if build == @build) }
= link_to ci_project_build_url(@project, build) do
= ci_icon_for_status(build.status)
......@@ -12,7 +12,7 @@
= build.id
- unless @commit.builds_without_retry.include?(@build)
- unless @commit.builds_without_retry_for_ref(@build.ref).include?(@build)
%li.active
%a
Build ##{@build.id}
......@@ -122,11 +122,6 @@
Commit
.pull-right
%small #{build_commit_link @build}
- if @build.commit.compare?
%p
%span.attr-name Compare:
#{build_compare_link @build}
%p
%span.attr-name Branch:
#{build_ref_link @build}
......
......@@ -7,7 +7,7 @@
%td.build-link
= link_to ci_project_ref_commits_path(commit.project, commit.ref, commit.sha) do
= link_to ci_project_commits_path(commit.project, commit.sha) do
%strong #{commit.short_sha}
%td.build-message
......@@ -16,7 +16,7 @@
%td.build-branch
- unless @ref
%span
= link_to truncate(commit.ref, length: 25), ci_project_path(@project, ref: commit.ref)
= link_to truncate(commit.last_ref, length: 25), ci_project_path(@project, ref: commit.last_ref)
%td.duration
- if commit.duration > 0
......
......@@ -9,19 +9,11 @@
.gray-content-block.second-block
.row
.col-sm-6
- if @commit.compare?
%p
%span.attr-name Compare:
#{gitlab_compare_link(@project, @commit.short_before_sha, @commit.short_sha)}
- else
%p
%span.attr-name Commit:
#{gitlab_commit_link(@project, @commit.sha)}
%p
%span.attr-name Branch:
#{gitlab_ref_link(@project, @commit.ref)}
.col-sm-6
- if @commit.git_author_name || @commit.git_author_email
%p
%span.attr-name Author:
#{@commit.git_author_name} (#{@commit.git_author_email})
......@@ -33,7 +25,7 @@
- if current_user && can?(current_user, :manage_builds, gl_project)
.pull-right
- if @commit.builds.running_or_pending.any?
= link_to "Cancel", cancel_ci_project_ref_commits_path(@project, @commit.ref, @commit.sha), class: 'btn btn-sm btn-danger'
= link_to "Cancel", cancel_ci_project_commits_path(@project, @commit), class: 'btn btn-sm btn-danger'
- if @commit.yaml_errors.present?
......@@ -43,18 +35,19 @@
- @commit.yaml_errors.split(",").each do |error|
%li= error
- unless @commit.push_data[:ci_yaml_file]
- unless @commit.ci_yaml_file
.bs-callout.bs-callout-warning
\.gitlab-ci.yml not found in this commit
%h3
Builds
- if @commit.duration > 0
- @commit.refs.each do |ref|
%h3
Builds for #{ref}
- if @commit.duration_for_ref(ref) > 0
%small.pull-right
%i.fa.fa-time
#{time_interval_in_words @commit.duration}
#{time_interval_in_words @commit.duration_for_ref(ref)}
%table.table.builds
%table.table.builds
%thead
%tr
%th Status
......@@ -66,7 +59,7 @@
- if @project.coverage_enabled?
%th Coverage
%th
= render @commit.builds_without_retry_sorted, controls: true
= render @commit.builds_without_retry.for_ref(ref), controls: true
- if @commit.retried_builds.any?
%h3
......@@ -77,6 +70,7 @@
%tr
%th Status
%th Build ID
%th Ref
%th Stage
%th Name
%th Duration
......@@ -84,4 +78,4 @@
- if @project.coverage_enabled?
%th Coverage
%th
= render @commit.retried_builds
= render @commit.retried_builds, ref: true
......@@ -11,7 +11,7 @@
%p
Author: #{@build.commit.git_author_name}
%p
Branch: #{@build.commit.ref}
Branch: #{@build.ref}
%p
Message: #{@build.commit.git_commit_message}
......
......@@ -3,7 +3,7 @@ Build failed for <%= @project.name %>
Status: <%= @build.status %>
Commit: <%= @build.commit.short_sha %>
Author: <%= @build.commit.git_author_name %>
Branch: <%= @build.commit.ref %>
Branch: <%= @build.ref %>
Message: <%= @build.commit.git_commit_message %>
Url: <%= ci_project_build_url(@build.project, @build) %>
......@@ -12,7 +12,7 @@
%p
Author: #{@build.commit.git_author_name}
%p
Branch: #{@build.commit.ref}
Branch: #{@build.ref}
%p
Message: #{@build.commit.git_commit_message}
......
......@@ -3,7 +3,7 @@ Build successful for <%= @project.name %>
Status: <%= @build.status %>
Commit: <%= @build.commit.short_sha %>
Author: <%= @build.commit.git_author_name %>
Branch: <%= @build.commit.ref %>
Branch: <%= @build.ref %>
Message: <%= @build.commit.git_commit_message %>
Url: <%= ci_project_build_url(@build.project, @build) %>
......@@ -30,14 +30,12 @@ Gitlab::Application.routes.draw do
resource :charts, only: [:show]
resources :refs, constraints: { ref_id: /.*/ }, only: [] do
resources :commits, only: [:show] do
member do
get :status
get :cancel
end
end
end
resources :builds, only: [:show] do
member do
......
class AddStageIdxToBuilds < ActiveRecord::Migration
def change
add_column :ci_builds, :stage_idx, :integer
end
end
class AddIndexForBuilds < ActiveRecord::Migration
def up
add_index :ci_builds, [:commit_id, :stage_idx, :created_at]
end
end
class AddRefAndTagToBuilds < ActiveRecord::Migration
def change
add_column :ci_builds, :tag, :boolean
add_column :ci_builds, :ref, :string
end
end
class MigrateRefAndTagToBuild < ActiveRecord::Migration
def change
execute('UPDATE ci_builds SET ref=(SELECT ref FROM ci_commits WHERE ci_commits.id = ci_builds.commit_id) WHERE ref IS NULL')
execute('UPDATE ci_builds SET tag=(SELECT tag FROM ci_commits WHERE ci_commits.id = ci_builds.commit_id) WHERE tag IS NULL')
end
end
class AddUserIdToBuild < ActiveRecord::Migration
def change
add_column :ci_builds, :user_id, :integer
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150930095736) do
ActiveRecord::Schema.define(version: 20151005075649) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -100,8 +100,13 @@ ActiveRecord::Schema.define(version: 20150930095736) do
t.boolean "allow_failure", default: false, null: false
t.string "stage"
t.integer "trigger_request_id"
t.integer "stage_idx"
t.boolean "tag"
t.string "ref"
t.integer "user_id"
end
add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree
add_index "ci_builds", ["project_id", "commit_id"], name: "index_ci_builds_on_project_id_and_commit_id", using: :btree
add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
......
......@@ -51,7 +51,7 @@ module Ci
required_attributes! [:project_id, :data, :project_token]
project = Ci::Project.find(params[:project_id])
authenticate_project_token!(project)
commit = Ci::CreateCommitService.new.execute(project, params[:data])
commit = Ci::CreateCommitService.new.execute(project, current_user, params[:data])
if commit.persisted?
present commit, with: Entities::CommitWithBuilds
......
......@@ -85,9 +85,10 @@ module Ci
def build_job(name, job)
{
stage_idx: stages.index(job[:stage]),
stage: job[:stage],
script: "#{@before_script.join("\n")}\n#{normalize_script(job[:script])}",
tags: job[:tags] || [],
commands: "#{@before_script.join("\n")}\n#{normalize_script(job[:script])}",
tag_list: job[:tags] || [],
name: name,
only: job[:only],
except: job[:except],
......
......@@ -27,6 +27,8 @@
FactoryGirl.define do
factory :ci_build, class: Ci::Build do
ref 'master'
tag false
started_at 'Di 29. Okt 09:51:28 CET 2013'
finished_at 'Di 29. Okt 09:53:28 CET 2013'
commands 'ls -a'
......@@ -43,5 +45,9 @@ FactoryGirl.define do
started_at nil
finished_at nil
end
factory :ci_build_tag do
tag true
end
end
end
......@@ -17,60 +17,32 @@
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :ci_commit, class: Ci::Commit do
ref 'master'
before_sha '76de212e80737a608d939f648d959671fb0a0142'
factory :ci_empty_commit, class: Ci::Commit do
sha '97de212e80737a608d939f648d959671fb0a0142'
push_data do
{
ref: 'refs/heads/master',
before: '76de212e80737a608d939f648d959671fb0a0142',
after: '97de212e80737a608d939f648d959671fb0a0142',
user_name: 'Git User',
user_email: 'git@example.com',
repository: {
name: 'test-data',
url: 'ssh://git@gitlab.com/test/test-data.git',
description: '',
homepage: 'http://gitlab.com/test/test-data'
},
commits: [
{
id: '97de212e80737a608d939f648d959671fb0a0142',
message: 'Test commit message',
timestamp: '2014-09-23T13:12:25+02:00',
url: 'https://gitlab.com/test/test-data/commit/97de212e80737a608d939f648d959671fb0a0142',
author: {
name: 'Git User',
email: 'git@user.com'
}
}
],
total_commits_count: 1,
ci_yaml_file: File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
}
end
gl_project factory: :empty_project
factory :ci_commit_without_jobs do
after(:create) do |commit, evaluator|
commit.push_data[:ci_yaml_file] = YAML.dump({})
commit.save
after(:build) do |commit|
allow(commit).to receive(:ci_yaml_file) { YAML.dump({}) }
end
end
factory :ci_commit_with_one_job do
after(:create) do |commit, evaluator|
commit.push_data[:ci_yaml_file] = YAML.dump({ rspec: { script: "ls" } })
commit.save
after(:build) do |commit|
allow(commit).to receive(:ci_yaml_file) { YAML.dump({ rspec: { script: "ls" } }) }
end
end
factory :ci_commit_with_two_jobs do
after(:create) do |commit, evaluator|
commit.push_data[:ci_yaml_file] = YAML.dump({ rspec: { script: "ls" }, spinach: { script: "ls" } })
commit.save
after(:build) do |commit|
allow(commit).to receive(:ci_yaml_file) { YAML.dump({ rspec: { script: "ls" }, spinach: { script: "ls" } }) }
end
end
factory :ci_commit do
after(:build) do |commit|
allow(commit).to receive(:ci_yaml_file) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) }
end
end
end
......
......@@ -11,6 +11,10 @@ describe "Commits" do
@commit.project.gl_project.team << [@user, :master]
end
before do
stub_ci_commit_to_return_yaml_file
end
describe "GET /:project/commits/:sha" do
before do
visit ci_commit_path(@commit)
......@@ -38,8 +42,7 @@ describe "Commits" do
end
it "shows warning" do
@commit.push_data[:ci_yaml_file] = nil
@commit.save
stub_ci_commit_yaml_file(nil)
visit ci_commit_path(@commit)
......
......@@ -17,11 +17,12 @@ module Ci
expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref(type, "master").first).to eq({
stage: "test",
stage_idx: 1,
except: nil,
name: :rspec,
only: nil,
script: "pwd\nrspec",
tags: [],
commands: "pwd\nrspec",
tag_list: [],
options: {},
allow_failure: false
})
......@@ -115,10 +116,11 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
except: nil,
stage: "test",
stage_idx: 1,
name: :rspec,
only: nil,
script: "pwd\nrspec",
tags: [],
commands: "pwd\nrspec",
tag_list: [],
options: {
image: "ruby:2.1",
services: ["mysql"]
......@@ -141,10 +143,11 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
except: nil,
stage: "test",
stage_idx: 1,
name: :rspec,
only: nil,
script: "pwd\nrspec",
tags: [],
commands: "pwd\nrspec",
tag_list: [],
options: {
image: "ruby:2.5",
services: ["postgresql"]
......
......@@ -30,9 +30,12 @@ describe Ci::Build do
let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project }
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
let(:build) { FactoryGirl.create :ci_build, commit: commit }
subject { build }
it { is_expected.to belong_to(:commit) }
it { is_expected.to belong_to(:user) }
it { is_expected.to validate_presence_of :status }
it { is_expected.to validate_presence_of :ref }
it { is_expected.to respond_to :success? }
it { is_expected.to respond_to :failed? }
......@@ -236,12 +239,6 @@ describe Ci::Build do
it { is_expected.to eq(options) }
end
describe :ref do
subject { build.ref }
it { is_expected.to eq(commit.ref) }
end
describe :sha do
subject { build.sha }
......@@ -254,12 +251,6 @@ describe Ci::Build do
it { is_expected.to eq(commit.short_sha) }
end
describe :before_sha do
subject { build.before_sha }
it { is_expected.to eq(commit.before_sha) }
end
describe :allow_git_fetch do
subject { build.allow_git_fetch }
......@@ -359,4 +350,38 @@ describe Ci::Build do
end
end
end
describe :project_recipients do
let(:pusher_email) { 'pusher@gitlab.test' }
let(:user) { User.new(notification_email: pusher_email) }
subject { build.project_recipients }
before do
build.update_attributes(user: user)
end
it 'should return pusher_email as only recipient when no additional recipients are given' do
project.update_attributes(email_add_pusher: true,
email_recipients: '')
is_expected.to eq([pusher_email])
end
it 'should return pusher_email and additional recipients' do
project.update_attributes(email_add_pusher: true,
email_recipients: 'rec1 rec2')
is_expected.to eq(['rec1', 'rec2', pusher_email])
end
it 'should return recipients' do
project.update_attributes(email_add_pusher: false,
email_recipients: 'rec1 rec2')
is_expected.to eq(['rec1', 'rec2'])
end
it 'should return unique recipients only' do
project.update_attributes(email_add_pusher: true,
email_recipients: "rec1 rec1 #{pusher_email}")
is_expected.to eq(['rec1', pusher_email])
end
end
end
......@@ -21,15 +21,10 @@ describe Ci::Commit do
let(:project) { FactoryGirl.create :ci_project }
let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project }
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
let(:commit_with_project) { FactoryGirl.create :ci_commit, gl_project: gl_project }
let(:config_processor) { Ci::GitlabCiYamlProcessor.new(gitlab_ci_yaml) }
it { is_expected.to belong_to(:gl_project) }
it { is_expected.to have_many(:builds) }
it { is_expected.to validate_presence_of :before_sha }
it { is_expected.to validate_presence_of :sha }
it { is_expected.to validate_presence_of :ref }
it { is_expected.to validate_presence_of :push_data }
it { is_expected.to respond_to :git_author_name }
it { is_expected.to respond_to :git_author_email }
......@@ -59,53 +54,6 @@ describe Ci::Commit do
end
end
describe :project_recipients do
context 'always sending notification' do
it 'should return commit_pusher_email as only recipient when no additional recipients are given' do
project = FactoryGirl.create :ci_project,
email_add_pusher: true,
email_recipients: ''
gl_project = FactoryGirl.create :empty_project, gitlab_ci_project: project
commit = FactoryGirl.create :ci_commit, gl_project: gl_project
expected = 'commit_pusher_email'
allow(commit).to receive(:push_data) { { user_email: expected } }
expect(commit.project_recipients).to eq([expected])
end
it 'should return commit_pusher_email and additional recipients' do
project = FactoryGirl.create :ci_project,
email_add_pusher: true,
email_recipients: 'rec1 rec2'
gl_project = FactoryGirl.create :empty_project, gitlab_ci_project: project
commit = FactoryGirl.create :ci_commit, gl_project: gl_project
expected = 'commit_pusher_email'
allow(commit).to receive(:push_data) { { user_email: expected } }
expect(commit.project_recipients).to eq(['rec1', 'rec2', expected])
end
it 'should return recipients' do
project = FactoryGirl.create :ci_project,
email_add_pusher: false,
email_recipients: 'rec1 rec2'
gl_project = FactoryGirl.create :empty_project, gitlab_ci_project: project
commit = FactoryGirl.create :ci_commit, gl_project: gl_project
expect(commit.project_recipients).to eq(['rec1', 'rec2'])
end
it 'should return unique recipients only' do
project = FactoryGirl.create :ci_project,
email_add_pusher: true,
email_recipients: 'rec1 rec1 rec2'
gl_project = FactoryGirl.create :empty_project, gitlab_ci_project: project
commit = FactoryGirl.create :ci_commit, gl_project: gl_project
expected = 'rec2'
allow(commit).to receive(:push_data) { { user_email: expected } }
expect(commit.project_recipients).to eq(['rec1', 'rec2'])
end
end
end
describe :valid_commit_sha do
context 'commit.sha can not start with 00000000' do
before do
......@@ -117,63 +65,95 @@ describe Ci::Commit do
end
end
describe :compare? do
subject { commit_with_project.compare? }
describe :short_sha do
subject { commit.short_sha }
context 'if commit.before_sha are not nil' do
it { is_expected.to be_truthy }
it 'has 8 items' do
expect(subject.size).to eq(8)
end
it { expect(commit.sha).to start_with(subject) }
end
describe :short_sha do
subject { commit.short_before_sha }
describe :stage do
subject { commit.stage }
it 'has 8 items' do
expect(subject.size).to eq(8)
before do
@second = FactoryGirl.create :ci_build, commit: commit, name: 'deploy', stage: 'deploy', stage_idx: 1, status: :pending
@first = FactoryGirl.create :ci_build, commit: commit, name: 'test', stage: 'test', stage_idx: 0, status: :pending
end
it { expect(commit.before_sha).to start_with(subject) }
it 'returns first running stage' do
is_expected.to eq('test')
end
describe :short_sha do
subject { commit.short_sha }
context 'first build succeeded' do
before do
@first.update_attributes(status: :success)
end
it 'has 8 items' do
expect(subject.size).to eq(8)
it 'returns last running stage' do
is_expected.to eq('deploy')
end
it { expect(commit.sha).to start_with(subject) }
end
describe :create_next_builds do
context 'all builds succeeded' do
before do
allow(commit).to receive(:config_processor).and_return(config_processor)
@first.update_attributes(status: :success)
@second.update_attributes(status: :success)
end
it 'returns nil' do
is_expected.to be_nil
end
end
end
describe :create_next_builds do
end
describe :create_builds do
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
def create_builds(trigger_request = nil)
commit.create_builds('master', false, nil, trigger_request)
end
it "creates builds for next type" do
expect(commit.create_builds).to be_truthy
def create_next_builds(trigger_request = nil)
commit.create_next_builds('master', false, nil, trigger_request)
end
it 'creates builds' do
expect(create_builds).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(2)
expect(commit.create_next_builds(nil)).to be_truthy
expect(create_next_builds).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(4)
expect(commit.create_next_builds(nil)).to be_truthy
expect(create_next_builds).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(5)
expect(commit.create_next_builds(nil)).to be_falsey
end
expect(create_next_builds).to be_falsey
end
describe :create_builds do
before do
allow(commit).to receive(:config_processor).and_return(config_processor)
context 'for different ref' do
def create_develop_builds
commit.create_builds('develop', false, nil, nil)
end
it 'creates builds' do
expect(commit.create_builds).to be_truthy
expect(create_builds).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(2)
expect(create_develop_builds).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(4)
expect(commit.refs.size).to eq(2)
expect(commit.builds.pluck(:name).uniq.size).to eq(2)
end
end
context 'for build triggers' do
......@@ -181,40 +161,39 @@ describe Ci::Commit do
let(:trigger_request) { FactoryGirl.create :ci_trigger_request, commit: commit, trigger: trigger }
it 'creates builds' do
expect(commit.create_builds(trigger_request)).to be_truthy
expect(create_builds(trigger_request)).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(2)
end
it 'rebuilds commit' do
expect(commit.create_builds).to be_truthy
expect(create_builds).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(2)
expect(commit.create_builds(trigger_request)).to be_truthy
expect(create_builds(trigger_request)).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(4)
end
it 'creates next builds' do
expect(commit.create_builds(trigger_request)).to be_truthy
expect(create_builds(trigger_request)).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(2)
expect(commit.create_next_builds(trigger_request)).to be_truthy
expect(create_next_builds(trigger_request)).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(4)
end
context 'for [ci skip]' do
before do
commit.push_data[:commits][0][:message] = 'skip this commit [ci skip]'
commit.save
allow(commit).to receive(:git_commit_message) { 'message [ci skip]' }
end
it 'rebuilds commit' do
expect(commit.status).to eq('skipped')
expect(commit.create_builds(trigger_request)).to be_truthy
expect(create_builds(trigger_request)).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(2)
expect(commit.status).to eq('pending')
......@@ -270,4 +249,59 @@ describe Ci::Commit do
expect(commit.coverage).to be_nil
end
end
describe :should_create_next_builds? do
before do
@build1 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: false, status: :success
@build2 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'develop', tag: false, status: :failed
@build3 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: true, status: :failed
@build4 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: :success
end
context 'for success' do
it 'to create if all succeeded' do
expect(commit.should_create_next_builds?(@build4)).to be_truthy
end
end
context 'for failed' do
before do
@build4.update_attributes(status: :failed)
end
it 'to not create' do
expect(commit.should_create_next_builds?(@build4)).to be_falsey
end
context 'and ignore failures for current' do
before do
@build4.update_attributes(allow_failure: true)
end
it 'to create' do
expect(commit.should_create_next_builds?(@build4)).to be_truthy
end
end
end
context 'for running' do
before do
@build4.update_attributes(status: :running)
end
it 'to not create' do
expect(commit.should_create_next_builds?(@build4)).to be_falsey
end
end
context 'for retried' do
before do
@build5 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: :failed
end
it 'to not create' do
expect(commit.should_create_next_builds?(@build4)).to be_falsey
end
end
end
end
......@@ -3,38 +3,6 @@ require 'spec_helper'
describe Ci::HipChatMessage do
subject { Ci::HipChatMessage.new(build) }
context "One build" do
let(:commit) { FactoryGirl.create(:ci_commit_with_one_job) }
let(:build) do
commit.create_builds
commit.builds.first
end
context 'when build succeeds' do
it 'returns a successful message' do
build.update(status: "success")
expect( subject.status_color ).to eq 'green'
expect( subject.notify? ).to be_falsey
expect( subject.to_s ).to match(/Build '[^']+' #\d+/)
expect( subject.to_s ).to match(/Successful in \d+ second\(s\)\./)
end
end
context 'when build fails' do
it 'returns a failure message' do
build.update(status: "failed")
expect( subject.status_color ).to eq 'red'
expect( subject.notify? ).to be_truthy
expect( subject.to_s ).to match(/Build '[^']+' #\d+/)
expect( subject.to_s ).to match(/Failed in \d+ second\(s\)\./)
end
end
end
context "Several builds" do
let(:commit) { FactoryGirl.create(:ci_commit_with_two_jobs) }
let(:build) do
......@@ -43,30 +11,29 @@ describe Ci::HipChatMessage do
context 'when all matrix builds succeed' do
it 'returns a successful message' do
commit.create_builds
commit.create_builds('master', false, nil)
commit.builds.update_all(status: "success")
commit.reload
expect( subject.status_color ).to eq 'green'
expect( subject.notify? ).to be_falsey
expect( subject.to_s ).to match(/Commit #\d+/)
expect( subject.to_s ).to match(/Successful in \d+ second\(s\)\./)
expect(subject.status_color).to eq 'green'
expect(subject.notify?).to be_falsey
expect(subject.to_s).to match(/Commit #\d+/)
expect(subject.to_s).to match(/Successful in \d+ second\(s\)\./)
end
end
context 'when at least one matrix build fails' do
it 'returns a failure message' do
commit.create_builds
commit.create_builds('master', false, nil)
first_build = commit.builds.first
second_build = commit.builds.last
first_build.update(status: "success")
second_build.update(status: "failed")
expect( subject.status_color ).to eq 'red'
expect( subject.notify? ).to be_truthy
expect( subject.to_s ).to match(/Commit #\d+/)
expect( subject.to_s ).to match(/Failed in \d+ second\(s\)\./)
end
expect(subject.status_color).to eq 'red'
expect(subject.notify?).to be_truthy
expect(subject.to_s).to match(/Commit #\d+/)
expect(subject.to_s).to match(/Failed in \d+ second\(s\)\./)
end
end
end
......@@ -29,12 +29,13 @@ describe Ci::MailService do
describe 'Sends email for' do
let(:mail) { Ci::MailService.new }
let(:user) { User.new(notification_email: 'git@example.com')}
describe 'failed build' do
let(:project) { FactoryGirl.create(:ci_project, email_add_pusher: true) }
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: :failed, commit: commit) }
let(:build) { FactoryGirl.create(:ci_build, status: :failed, commit: commit, user: user) }
before do
allow(mail).to receive_messages(
......@@ -57,7 +58,7 @@ describe Ci::MailService do
let(:project) { FactoryGirl.create(:ci_project, email_add_pusher: true, email_only_broken_builds: false) }
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit) }
let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit, user: user) }
before do
allow(mail).to receive_messages(
......@@ -85,7 +86,7 @@ describe Ci::MailService do
end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit) }
let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit, user: user) }
before do
allow(mail).to receive_messages(
......@@ -114,7 +115,7 @@ describe Ci::MailService do
end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit) }
let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit, user: user) }
before do
allow(mail).to receive_messages(
......@@ -143,7 +144,7 @@ describe Ci::MailService do
end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit) }
let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit, user: user) }
before do
allow(mail).to receive_messages(
......@@ -166,7 +167,7 @@ describe Ci::MailService do
end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: :failed, commit: commit) }
let(:build) { FactoryGirl.create(:ci_build, status: :failed, commit: commit, user: user) }
before do
allow(mail).to receive_messages(
......
......@@ -3,51 +3,13 @@ require 'spec_helper'
describe Ci::SlackMessage do
subject { Ci::SlackMessage.new(commit) }
context "One build" do
let(:commit) { FactoryGirl.create(:ci_commit_with_one_job) }
let(:build) do
commit.create_builds
commit.builds.first
end
context 'when build succeeded' do
let(:color) { 'good' }
it 'returns a message with succeeded build' do
build.update(status: "success")
expect(subject.color).to eq(color)
expect(subject.fallback).to include('Build')
expect(subject.fallback).to include("\##{build.id}")
expect(subject.fallback).to include('succeeded')
expect(subject.attachments.first[:fields]).to be_empty
end
end
context 'when build failed' do
let(:color) { 'danger' }
it 'returns a message with failed build' do
build.update(status: "failed")
expect(subject.color).to eq(color)
expect(subject.fallback).to include('Build')
expect(subject.fallback).to include("\##{build.id}")
expect(subject.fallback).to include('failed')
expect(subject.attachments.first[:fields]).to be_empty
end
end
end
context "Several builds" do
let(:commit) { FactoryGirl.create(:ci_commit_with_two_jobs) }
context 'when all matrix builds succeeded' do
let(:color) { 'good' }
it 'returns a message with success' do
commit.create_builds
commit.create_builds('master', false, nil)
commit.builds.update_all(status: "success")
commit.reload
......@@ -63,7 +25,7 @@ describe Ci::SlackMessage do
let(:color) { 'danger' }
it 'returns a message with information about failed build' do
commit.create_builds
commit.create_builds('master', false, nil)
first_build = commit.builds.first
second_build = commit.builds.last
first_build.update(status: "success")
......@@ -78,5 +40,4 @@ describe Ci::SlackMessage do
expect(subject.attachments.first[:fields].first[:value]).to include("\##{second_build.id}")
end
end
end
end
......@@ -39,8 +39,8 @@ describe GitlabCiService do
end
describe :build_page do
it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://localhost/ci/projects/#{@ci_project.id}/refs/master/commits/2ab7834c")}
it { expect(@service.build_page("issue#2", 'master')).to eq("http://localhost/ci/projects/#{@ci_project.id}/refs/master/commits/issue%232")}
it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://localhost/ci/projects/#{@ci_project.id}/commits/2ab7834c")}
it { expect(@service.build_page("issue#2", 'master')).to eq("http://localhost/ci/projects/#{@ci_project.id}/commits/issue%232")}
end
describe "execute" do
......@@ -48,8 +48,8 @@ describe GitlabCiService do
let(:project) { create(:project, name: 'project') }
let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
it "calls ci_yaml_file" do
expect(@service).to receive(:ci_yaml_file).with(push_sample_data[:checkout_sha])
it "calls CreateCommitService" do
expect_any_instance_of(Ci::CreateCommitService).to receive(:execute).with(@ci_project, user, push_sample_data)
@service.execute(push_sample_data)
end
......
......@@ -7,6 +7,10 @@ describe Ci::API::API do
let(:project) { FactoryGirl.create(:ci_project) }
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
before do
stub_ci_commit_to_return_yaml_file
end
describe "Builds API for runners" do
let(:shared_runner) { FactoryGirl.create(:ci_runner, token: "SharedRunner") }
let(:shared_project) { FactoryGirl.create(:ci_project, name: "SharedProject") }
......@@ -19,7 +23,7 @@ describe Ci::API::API do
describe "POST /builds/register" do
it "should start a build" do
commit = FactoryGirl.create(:ci_commit, gl_project: gl_project)
commit.create_builds
commit.create_builds('master', false, nil)
build = commit.builds.first
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
......@@ -55,7 +59,7 @@ describe Ci::API::API do
it "returns options" do
commit = FactoryGirl.create(:ci_commit, gl_project: gl_project)
commit.create_builds
commit.create_builds('master', false, nil)
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
......@@ -65,7 +69,7 @@ describe Ci::API::API do
it "returns variables" do
commit = FactoryGirl.create(:ci_commit, gl_project: gl_project)
commit.create_builds
commit.create_builds('master', false, nil)
project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
......@@ -82,7 +86,7 @@ describe Ci::API::API do
commit = FactoryGirl.create(:ci_commit, gl_project: gl_project)
trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, commit: commit, trigger: trigger)
commit.create_builds(trigger_request)
commit.create_builds('master', false, nil, trigger_request)
project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
......
......@@ -44,8 +44,7 @@ describe Ci::API::API, 'Commits' do
"email" => "jordi@softcatala.org",
}
}
],
ci_yaml_file: gitlab_ci_yaml
]
}
end
......
......@@ -5,8 +5,8 @@ describe Ci::API::API do
describe 'POST /projects/:project_id/refs/:ref/trigger' do
let!(:trigger_token) { 'secure token' }
let!(:project) { FactoryGirl.create(:ci_project) }
let!(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let!(:gl_project) { FactoryGirl.create(:project) }
let!(:project) { FactoryGirl.create(:ci_project, gl_project: gl_project) }
let!(:project2) { FactoryGirl.create(:ci_project) }
let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) }
let(:options) do
......@@ -15,6 +15,10 @@ describe Ci::API::API do
}
end
before do
stub_ci_commit_to_return_yaml_file
end
context 'Handles errors' do
it 'should return bad request if token is missing' do
post ci_api("/projects/#{project.id}/refs/master/trigger")
......@@ -33,15 +37,13 @@ describe Ci::API::API do
end
context 'Have a commit' do
before do
@commit = FactoryGirl.create(:ci_commit, gl_project: gl_project)
end
let(:commit) { project.commits.last }
it 'should create builds' do
post ci_api("/projects/#{project.id}/refs/master/trigger"), options
expect(response.status).to eq(201)
@commit.builds.reload
expect(@commit.builds.size).to eq(2)
commit.builds.reload
expect(commit.builds.size).to eq(2)
end
it 'should return bad request with no builds created if there\'s no commit for that ref' do
......@@ -70,8 +72,8 @@ describe Ci::API::API do
it 'create trigger request with variables' do
post ci_api("/projects/#{project.id}/refs/master/trigger"), options.merge(variables: variables)
expect(response.status).to eq(201)
@commit.builds.reload
expect(@commit.builds.first.trigger_request.variables).to eq(variables)
commit.builds.reload
expect(commit.builds.first.trigger_request.variables).to eq(variables)
end
end
end
......
......@@ -7,7 +7,7 @@ describe "Commits" do
describe "GET /:project/refs/:ref_name/commits/:id/status.json" do
before do
get status_ci_project_ref_commits_path(@commit.project, @commit.ref, @commit.sha), format: :json
get status_ci_project_commits_path(@commit.project, @commit.sha), format: :json
end
it { expect(response.status).to eq(200) }
......
......@@ -4,15 +4,19 @@ module Ci
describe CreateCommitService do
let(:service) { CreateCommitService.new }
let(:project) { FactoryGirl.create(:ci_project) }
let(:user) { nil }
before do
stub_ci_commit_to_return_yaml_file
end
describe :execute do
context 'valid params' do
let(:commit) do
service.execute(project,
service.execute(project, user,
ref: 'refs/heads/master',
before: '00000000',
after: '31das312',
ci_yaml_file: gitlab_ci_yaml,
commits: [ { message: "Message" } ]
)
end
......@@ -26,11 +30,10 @@ module Ci
context "skip tag if there is no build for it" do
it "creates commit if there is appropriate job" do
result = service.execute(project,
result = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
ci_yaml_file: gitlab_ci_yaml,
commits: [ { message: "Message" } ]
)
expect(result).to be_persisted
......@@ -38,12 +41,12 @@ module Ci
it "creates commit if there is no appropriate job but deploy job has right ref setting" do
config = YAML.dump({ deploy: { deploy: "ls", only: ["0_1"] } })
stub_ci_commit_yaml_file(config)
result = service.execute(project,
result = service.execute(project, user,
ref: 'refs/heads/0_1',
before: '00000000',
after: '31das312',
ci_yaml_file: config,
commits: [ { message: "Message" } ]
)
expect(result).to be_persisted
......@@ -51,11 +54,11 @@ module Ci
end
it 'fails commits without .gitlab-ci.yml' do
result = service.execute(project,
stub_ci_commit_yaml_file(nil)
result = service.execute(project, user,
ref: 'refs/heads/0_1',
before: '00000000',
after: '31das312',
ci_yaml_file: config,
commits: [ { message: 'Message' } ]
)
expect(result).to be_persisted
......@@ -64,41 +67,46 @@ module Ci
end
describe :ci_skip? do
let(:message) { "some message[ci skip]" }
before do
allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message }
end
it "skips builds creation if there is [ci skip] tag in commit message" do
commits = [{ message: "some message[ci skip]" }]
commit = service.execute(project,
commits = [{ message: message }]
commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits,
ci_yaml_file: gitlab_ci_yaml
commits: commits
)
expect(commit.builds.any?).to be false
expect(commit.status).to eq("skipped")
end
it "does not skips builds creation if there is no [ci skip] tag in commit message" do
commits = [{ message: "some message" }]
allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { "some message" }
commit = service.execute(project,
commits = [{ message: "some message" }]
commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits,
ci_yaml_file: gitlab_ci_yaml
commits: commits
)
expect(commit.builds.first.name).to eq("staging")
end
it "skips builds creation if there is [ci skip] tag in commit message and yaml is invalid" do
commits = [{ message: "some message[ci skip]" }]
commit = service.execute(project,
stub_ci_commit_yaml_file('invalid: file')
commits = [{ message: message }]
commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits,
ci_yaml_file: "invalid: file"
commits: commits
)
expect(commit.builds.any?).to be false
expect(commit.status).to eq("skipped")
......@@ -106,35 +114,36 @@ module Ci
end
it "skips build creation if there are already builds" do
allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file) { gitlab_ci_yaml }
commits = [{ message: "message" }]
commit = service.execute(project,
commit = service.execute(project, user,
ref: 'refs/heads/master',
before: '00000000',
after: '31das312',
commits: commits,
ci_yaml_file: gitlab_ci_yaml
commits: commits
)
expect(commit.builds.count(:all)).to eq(2)
commit = service.execute(project,
commit = service.execute(project, user,
ref: 'refs/heads/master',
before: '00000000',
after: '31das312',
commits: commits,
ci_yaml_file: gitlab_ci_yaml
commits: commits
)
expect(commit.builds.count(:all)).to eq(2)
end
it "creates commit with failed status if yaml is invalid" do
stub_ci_commit_yaml_file('invalid: file')
commits = [{ message: "some message" }]
commit = service.execute(project,
commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits,
ci_yaml_file: "invalid: file"
commits: commits
)
expect(commit.status).to eq("failed")
......
......@@ -2,20 +2,20 @@ require 'spec_helper'
describe Ci::CreateTriggerRequestService do
let(:service) { Ci::CreateTriggerRequestService.new }
let(:project) { FactoryGirl.create :ci_project }
let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project }
let(:trigger) { FactoryGirl.create :ci_trigger, project: project }
let(:gl_project) { create(:project) }
let(:project) { create(:ci_project, gl_project: gl_project) }
let(:trigger) { create(:ci_trigger, project: project) }
before do
stub_ci_commit_to_return_yaml_file
end
describe :execute do
context 'valid params' do
subject { service.execute(project, trigger, 'master') }
before do
@commit = FactoryGirl.create :ci_commit, gl_project: gl_project
end
it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
it { expect(subject.commit).to eq(@commit) }
it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
end
context 'no commit for ref' do
......@@ -28,26 +28,11 @@ describe Ci::CreateTriggerRequestService do
subject { service.execute(project, trigger, 'master') }
before do
FactoryGirl.create :ci_commit_without_jobs, gl_project: gl_project
stub_ci_commit_yaml_file('{}')
FactoryGirl.create :ci_commit, gl_project: gl_project
end
it { expect(subject).to be_nil }
end
context 'for multiple commits' do
subject { service.execute(project, trigger, 'master') }
before do
@commit1 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: gl_project
@commit2 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: gl_project
@commit3 = FactoryGirl.create :ci_commit, committed_at: 3.hour.ago, gl_project: gl_project
end
context 'retries latest one' do
it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
it { expect(subject).to be_persisted }
it { expect(subject.commit).to eq(@commit2) }
end
end
end
end
......@@ -43,4 +43,8 @@ RSpec.configure do |config|
end
end
FactoryGirl::SyntaxRunner.class_eval do
include RSpec::Mocks::ExampleMethods
end
ActiveRecord::Migration.maintain_test_schema!
......@@ -13,6 +13,14 @@ module StubGitlabCalls
allow_any_instance_of(Network).to receive(:projects) { project_hash_array }
end
def stub_ci_commit_to_return_yaml_file
stub_ci_commit_yaml_file(gitlab_ci_yaml)
end
def stub_ci_commit_yaml_file(ci_yaml)
allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file) { ci_yaml }
end
private
def gitlab_url
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment