Merge branch 'master' into 242-restart_last_build_list
This commit is contained in:
commit
c2ee66cb67
|
@ -290,6 +290,18 @@ class BuildList < ActiveRecord::Base
|
||||||
BuildList.where(:id => extra_build_lists).where('status != ?', BUILD_PUBLISHED).count == 0
|
BuildList.where(:id => extra_build_lists).where('status != ?', BUILD_PUBLISHED).count == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def human_average_build_time
|
||||||
|
I18n.t('layout.project_statistics.human_average_build_time', {:hours => (average_build_time/3600).to_i, :minutes => (average_build_time%3600/60).to_i})
|
||||||
|
end
|
||||||
|
|
||||||
|
def formatted_average_build_time
|
||||||
|
"%02d:%02d" % [average_build_time / 3600, average_build_time % 3600 / 60]
|
||||||
|
end
|
||||||
|
|
||||||
|
def average_build_time
|
||||||
|
project.project_statistics.where(:arch_id => arch_id).first.try(:average_build_time) || 0
|
||||||
|
end
|
||||||
|
|
||||||
def self.human_status(status)
|
def self.human_status(status)
|
||||||
I18n.t("layout.build_lists.statuses.#{HUMAN_STATUSES[status]}")
|
I18n.t("layout.build_lists.statuses.#{HUMAN_STATUSES[status]}")
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,9 +14,14 @@ class BuildListObserver < ActiveRecord::Observer
|
||||||
|
|
||||||
if record.status == BuildList::SUCCESS
|
if record.status == BuildList::SUCCESS
|
||||||
# Update project average build time
|
# Update project average build time
|
||||||
build_count = record.project.build_count
|
begin
|
||||||
new_av_time = ( record.project.average_build_time * build_count + record.duration ) / ( build_count + 1 )
|
statistic = record.project.project_statistics.find_or_create_by_arch_id(record.arch_id)
|
||||||
record.project.update_attributes({ :average_build_time => new_av_time, :build_count => build_count + 1 }, :without_protection => true)
|
rescue ActiveRecord::RecordNotUnique
|
||||||
|
retry
|
||||||
|
end
|
||||||
|
build_count = statistic.build_count
|
||||||
|
new_av_time = ( statistic.average_build_time * build_count + record.duration ) / ( build_count + 1 )
|
||||||
|
statistic.update_attributes(:average_build_time => new_av_time, :build_count => build_count + 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,6 +15,7 @@ class Project < ActiveRecord::Base
|
||||||
has_many :project_to_repositories, :dependent => :destroy
|
has_many :project_to_repositories, :dependent => :destroy
|
||||||
has_many :repositories, :through => :project_to_repositories
|
has_many :repositories, :through => :project_to_repositories
|
||||||
has_many :project_tags, :dependent => :destroy
|
has_many :project_tags, :dependent => :destroy
|
||||||
|
has_many :project_statistics, :dependent => :destroy
|
||||||
|
|
||||||
has_many :build_lists, :dependent => :destroy
|
has_many :build_lists, :dependent => :destroy
|
||||||
has_many :hooks, :dependent => :destroy
|
has_many :hooks, :dependent => :destroy
|
||||||
|
@ -193,14 +194,6 @@ class Project < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def human_average_build_time
|
|
||||||
I18n.t("layout.projects.human_average_build_time", {:hours => (average_build_time/3600).to_i, :minutes => (average_build_time%3600/60).to_i})
|
|
||||||
end
|
|
||||||
|
|
||||||
def formatted_average_build_time
|
|
||||||
"%02d:%02d" % [average_build_time / 3600, average_build_time % 3600 / 60]
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy_project_from_repository(repository)
|
def destroy_project_from_repository(repository)
|
||||||
AbfWorker::BuildListsPublishTaskManager.destroy_project_from_repository self, repository
|
AbfWorker::BuildListsPublishTaskManager.destroy_project_from_repository self, repository
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
class ProjectStatistic < ActiveRecord::Base
|
||||||
|
|
||||||
|
belongs_to :arch
|
||||||
|
belongs_to :project
|
||||||
|
|
||||||
|
validates :arch_id, :project_id, :average_build_time, :build_count, :presence => true
|
||||||
|
validates :project_id, :uniqueness => {:scope => :arch_id}
|
||||||
|
|
||||||
|
attr_accessible :average_build_time, :build_count
|
||||||
|
end
|
|
@ -1,6 +1,6 @@
|
||||||
json.projects @projects do |project|
|
json.projects @projects do |project|
|
||||||
json.partial! 'project', :project => project
|
json.partial! 'project', :project => project
|
||||||
json.(project, :visibility, :description, :ancestry, :has_issues, :has_wiki, :default_branch, :is_package, :average_build_time, :publish_i686_into_x86_64)
|
json.(project, :visibility, :description, :ancestry, :has_issues, :has_wiki, :default_branch, :is_package, :publish_i686_into_x86_64)
|
||||||
json.created_at project.created_at.to_i
|
json.created_at project.created_at.to_i
|
||||||
json.updated_at project.updated_at.to_i
|
json.updated_at project.updated_at.to_i
|
||||||
json.partial! 'api/v1/shared/owner', :owner => project.owner
|
json.partial! 'api/v1/shared/owner', :owner => project.owner
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
json.project do
|
json.project do
|
||||||
json.partial! 'project', :project => @project
|
json.partial! 'project', :project => @project
|
||||||
json.(@project, :visibility, :description, :ancestry, :has_issues, :has_wiki, :default_branch, :is_package, :average_build_time, :publish_i686_into_x86_64)
|
json.(@project, :visibility, :description, :ancestry, :has_issues, :has_wiki, :default_branch, :is_package, :publish_i686_into_x86_64)
|
||||||
json.created_at @project.created_at.to_i
|
json.created_at @project.created_at.to_i
|
||||||
json.updated_at @project.updated_at.to_i
|
json.updated_at @project.updated_at.to_i
|
||||||
json.partial! 'api/v1/shared/owner', :owner => @project.owner
|
json.partial! 'api/v1/shared/owner', :owner => @project.owner
|
||||||
json.maintainer do
|
json.maintainer do
|
||||||
json.partial! 'api/v1/shared/member', :member => @project.maintainer
|
json.partial! 'api/v1/shared/member', :member => @project.maintainer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
json.project_statistics @project.project_statistics do |statistic|
|
||||||
|
json.(statistic, :average_build_time, :build_count, :arch_id)
|
||||||
|
end
|
||||||
|
|
||||||
json.repositories @project.repositories do |repo|
|
json.repositories @project.repositories do |repo|
|
||||||
json.(repo, :id, :name)
|
json.(repo, :id, :name)
|
||||||
json.url api_v1_repository_path(repo.name, :format => :json)
|
json.url api_v1_repository_path(repo.name, :format => :json)
|
||||||
|
|
|
@ -6,10 +6,10 @@
|
||||||
- if BuildList::HUMAN_STATUSES[build_list.status].in? [:build_pending, :build_started, :build_publish]
|
- if BuildList::HUMAN_STATUSES[build_list.status].in? [:build_pending, :build_started, :build_publish]
|
||||||
%time.js-relative-date{:datetime => build_list.updated_at.strftime("%FT%T%:z"), :title => build_list.updated_at.strftime("%F %T")}
|
%time.js-relative-date{:datetime => build_list.updated_at.strftime("%FT%T%:z"), :title => build_list.updated_at.strftime("%F %T")}
|
||||||
= build_list.updated_at.strftime "%F %T"
|
= build_list.updated_at.strftime "%F %T"
|
||||||
- if build_list.build_started? && ((build_list.project.average_build_time || 0) > 0)
|
- if build_list.build_started? && (build_list.average_build_time > 0)
|
||||||
\/
|
\/
|
||||||
%time
|
%time
|
||||||
= build_list.project.formatted_average_build_time
|
= build_list.formatted_average_build_time
|
||||||
- if build_list.project.present?
|
- if build_list.project.present?
|
||||||
%td= link_to build_list.project.name_with_owner, build_list.project
|
%td= link_to build_list.project.name_with_owner, build_list.project
|
||||||
%td= build_list_version_link(build_list)
|
%td= build_list_version_link(build_list)
|
||||||
|
|
|
@ -98,7 +98,7 @@
|
||||||
%br
|
%br
|
||||||
.leftlist
|
.leftlist
|
||||||
.rightlist
|
.rightlist
|
||||||
= "#{@build_list.human_current_duration} / #{@build_list.project.human_average_build_time}"
|
= "#{@build_list.human_current_duration} / #{@build_list.human_average_build_time}"
|
||||||
.both
|
.both
|
||||||
|
|
||||||
- if @build_list.can_cancel? && can?(:cancel, @build_list)
|
- if @build_list.can_cancel? && can?(:cancel, @build_list)
|
||||||
|
|
|
@ -9,6 +9,9 @@ common: &common
|
||||||
- 127.0.0.100
|
- 127.0.0.100
|
||||||
abf_worker:
|
abf_worker:
|
||||||
publish_workers_count: 2
|
publish_workers_count: 2
|
||||||
|
log_server:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 6379
|
||||||
keys:
|
keys:
|
||||||
key_pair_secret_key: 'key_pair_secret_key'
|
key_pair_secret_key: 'key_pair_secret_key'
|
||||||
airbrake_api_key: 'airbrake_api_key'
|
airbrake_api_key: 'airbrake_api_key'
|
||||||
|
|
|
@ -9,6 +9,9 @@ common: &common
|
||||||
- 127.0.0.100
|
- 127.0.0.100
|
||||||
abf_worker:
|
abf_worker:
|
||||||
publish_workers_count: 2
|
publish_workers_count: 2
|
||||||
|
log_server:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 6379
|
||||||
keys:
|
keys:
|
||||||
key_pair_secret_key: 'key_pair_secret_key'
|
key_pair_secret_key: 'key_pair_secret_key'
|
||||||
airbrake_api_key: 'airbrake_api_key'
|
airbrake_api_key: 'airbrake_api_key'
|
||||||
|
|
|
@ -47,7 +47,6 @@ en:
|
||||||
sections: Sections
|
sections: Sections
|
||||||
has_issue_description: Tracker adds a lightweight issue management system tightly integrated with your repository.
|
has_issue_description: Tracker adds a lightweight issue management system tightly integrated with your repository.
|
||||||
has_wiki_description: Wikis are the simplest way to allow other users to contribute content. Any user can create and edit pages for documentation, examples, support or anything you wish.
|
has_wiki_description: Wikis are the simplest way to allow other users to contribute content. Any user can create and edit pages for documentation, examples, support or anything you wish.
|
||||||
human_average_build_time: Expected time is %{hours} h. %{minutes} min.
|
|
||||||
git_global_setup: Git global setup
|
git_global_setup: Git global setup
|
||||||
create_repository: Create Repository
|
create_repository: Create Repository
|
||||||
move_files_to_folder: Move files you need to the project or create them.
|
move_files_to_folder: Move files you need to the project or create them.
|
||||||
|
|
|
@ -3,7 +3,7 @@ ru:
|
||||||
projects:
|
projects:
|
||||||
branches: Ветки
|
branches: Ветки
|
||||||
delete_branch: Удалить ветку
|
delete_branch: Удалить ветку
|
||||||
restore_branch: Востановить ветку
|
restore_branch: Восстановить ветку
|
||||||
no_branches: Нет веток
|
no_branches: Нет веток
|
||||||
base_branch: Текущая ветка
|
base_branch: Текущая ветка
|
||||||
compare: Сравнить
|
compare: Сравнить
|
||||||
|
@ -47,7 +47,6 @@ ru:
|
||||||
sections: Разделы
|
sections: Разделы
|
||||||
has_issue_description: Трэкер предоставляет лекговесный менеджер для задач по разработке Вашего проекта.
|
has_issue_description: Трэкер предоставляет лекговесный менеджер для задач по разработке Вашего проекта.
|
||||||
has_wiki_description: Wiki - это самый простой способ предоставить другим вносить свой вклад в развитие Вашего проекта. Каждый пользователь нашего сервиса может использовать Wiki для документирования, примеров, поддержки или всего другого, в чем у Вас появится необходимость.
|
has_wiki_description: Wiki - это самый простой способ предоставить другим вносить свой вклад в развитие Вашего проекта. Каждый пользователь нашего сервиса может использовать Wiki для документирования, примеров, поддержки или всего другого, в чем у Вас появится необходимость.
|
||||||
human_average_build_time: 'Ожидаемое время: %{hours} ч. %{minutes} мин.'
|
|
||||||
git_global_setup: Общие настройки Git
|
git_global_setup: Общие настройки Git
|
||||||
create_repository: Создание репозитория
|
create_repository: Создание репозитория
|
||||||
move_files_to_folder: Переместите нужные файлы в проект или создайте их.
|
move_files_to_folder: Переместите нужные файлы в проект или создайте их.
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
en:
|
||||||
|
layout:
|
||||||
|
project_statistics:
|
||||||
|
human_average_build_time: Expected time is %{hours} h. %{minutes} min.
|
||||||
|
|
||||||
|
flash:
|
||||||
|
project_statistic:
|
||||||
|
|
||||||
|
|
||||||
|
activerecord:
|
||||||
|
models:
|
||||||
|
project_statistic: Project statistic
|
||||||
|
attributes:
|
||||||
|
project_statistic:
|
|
@ -0,0 +1,16 @@
|
||||||
|
ru:
|
||||||
|
layout:
|
||||||
|
project_statistics:
|
||||||
|
human_average_build_time: 'Ожидаемое время: %{hours} ч. %{minutes} мин.'
|
||||||
|
|
||||||
|
flash:
|
||||||
|
project_statistic:
|
||||||
|
|
||||||
|
|
||||||
|
activerecord:
|
||||||
|
models:
|
||||||
|
project_statistic: Статистика проекта
|
||||||
|
attributes:
|
||||||
|
project_statistic:
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
class CreateProjectStatistics < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :project_statistics do |t|
|
||||||
|
t.integer :average_build_time, :null => false, :default => 0
|
||||||
|
t.integer :build_count, :null => false, :default => 0
|
||||||
|
t.integer :arch_id, :null => false
|
||||||
|
t.integer :project_id, :null => false
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
add_index :project_statistics, [:project_id, :arch_id], :unique => true
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,11 @@
|
||||||
|
class DropAverageBuildTimeAndBuildCountFromProject < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
remove_column :projects, :average_build_time
|
||||||
|
remove_column :projects, :build_count
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
add_column :projects, :average_build_time, :integer, :null => false, :default => 0
|
||||||
|
add_column :projects, :build_count, :integer, :null => false, :default => 0
|
||||||
|
end
|
||||||
|
end
|
15
db/schema.rb
15
db/schema.rb
|
@ -11,7 +11,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended to check this file into your version control system.
|
# It's strongly recommended to check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(:version => 20130724105821) do
|
ActiveRecord::Schema.define(:version => 20130731130518) do
|
||||||
|
|
||||||
create_table "activity_feeds", :force => true do |t|
|
create_table "activity_feeds", :force => true do |t|
|
||||||
t.integer "user_id", :null => false
|
t.integer "user_id", :null => false
|
||||||
|
@ -376,6 +376,17 @@ ActiveRecord::Schema.define(:version => 20130724105821) do
|
||||||
|
|
||||||
add_index "project_imports", ["platform_id", "name"], :name => "index_project_imports_on_name_and_platform_id", :unique => true, :case_sensitive => false
|
add_index "project_imports", ["platform_id", "name"], :name => "index_project_imports_on_name_and_platform_id", :unique => true, :case_sensitive => false
|
||||||
|
|
||||||
|
create_table "project_statistics", :force => true do |t|
|
||||||
|
t.integer "average_build_time", :default => 0, :null => false
|
||||||
|
t.integer "build_count", :default => 0, :null => false
|
||||||
|
t.integer "arch_id", :null => false
|
||||||
|
t.integer "project_id", :null => false
|
||||||
|
t.datetime "created_at", :null => false
|
||||||
|
t.datetime "updated_at", :null => false
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "project_statistics", ["project_id", "arch_id"], :name => "index_project_statistics_on_project_id_and_arch_id", :unique => true
|
||||||
|
|
||||||
create_table "project_tags", :force => true do |t|
|
create_table "project_tags", :force => true do |t|
|
||||||
t.integer "project_id"
|
t.integer "project_id"
|
||||||
t.string "commit_id"
|
t.string "commit_id"
|
||||||
|
@ -412,8 +423,6 @@ ActiveRecord::Schema.define(:version => 20130724105821) do
|
||||||
t.boolean "has_wiki", :default => false
|
t.boolean "has_wiki", :default => false
|
||||||
t.string "default_branch", :default => "master"
|
t.string "default_branch", :default => "master"
|
||||||
t.boolean "is_package", :default => true, :null => false
|
t.boolean "is_package", :default => true, :null => false
|
||||||
t.integer "average_build_time", :default => 0, :null => false
|
|
||||||
t.integer "build_count", :default => 0, :null => false
|
|
||||||
t.integer "maintainer_id"
|
t.integer "maintainer_id"
|
||||||
t.boolean "publish_i686_into_x86_64", :default => false
|
t.boolean "publish_i686_into_x86_64", :default => false
|
||||||
t.string "owner_uname", :null => false
|
t.string "owner_uname", :null => false
|
||||||
|
|
|
@ -1,59 +1,70 @@
|
||||||
module AbfWorker
|
module AbfWorker::ModelHelper
|
||||||
module ModelHelper
|
# In model which contains this helper should be:
|
||||||
# In model which contains this helper should be:
|
# - #abf_worker_args
|
||||||
# - #abf_worker_args
|
# - #build_canceled
|
||||||
# - #build_canceled
|
|
||||||
|
|
||||||
def abf_worker_log
|
|
||||||
Resque.redis.get(service_queue) || I18n.t('layout.build_lists.log.not_available')
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_job_to_abf_worker_queue
|
|
||||||
Resque.push(
|
|
||||||
worker_queue_with_priority,
|
|
||||||
'class' => worker_queue_class,
|
|
||||||
'args' => [abf_worker_args]
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def cancel_job
|
|
||||||
deleted = Resque::Job.destroy(
|
|
||||||
worker_queue_with_priority,
|
|
||||||
worker_queue_class,
|
|
||||||
abf_worker_args
|
|
||||||
)
|
|
||||||
if deleted == 1
|
|
||||||
build_canceled
|
|
||||||
else
|
|
||||||
send_stop_signal
|
|
||||||
end
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def worker_queue_with_priority(queue = nil)
|
|
||||||
queue ||= abf_worker_base_queue
|
|
||||||
queue << '_' << abf_worker_priority if abf_worker_priority.present?
|
|
||||||
queue
|
|
||||||
end
|
|
||||||
|
|
||||||
def worker_queue_class(queue_class = nil)
|
|
||||||
queue_class ||= "AbfWorker::#{abf_worker_base_queue.classify}"
|
|
||||||
queue_class << abf_worker_priority.capitalize
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def send_stop_signal
|
|
||||||
Resque.redis.setex(
|
|
||||||
"#{service_queue}::live-inspector",
|
|
||||||
240, # Data will be removed from Redis after 240 sec.
|
|
||||||
'USR1' # Immediately kill child but don't exit
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def service_queue
|
|
||||||
"abfworker::#{abf_worker_base_queue.gsub(/\_/, '-')}-#{id}"
|
|
||||||
end
|
|
||||||
|
|
||||||
|
def self.included(base)
|
||||||
|
base.extend(ClassMethods)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def log_server
|
||||||
|
@log_server ||= Redis.new(
|
||||||
|
:host => APP_CONFIG['abf_worker']['log_server']['host'],
|
||||||
|
:port => APP_CONFIG['abf_worker']['log_server']['port']
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def abf_worker_log
|
||||||
|
self.class.log_server.get(service_queue) || I18n.t('layout.build_lists.log.not_available')
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_job_to_abf_worker_queue
|
||||||
|
Resque.push(
|
||||||
|
worker_queue_with_priority,
|
||||||
|
'class' => worker_queue_class,
|
||||||
|
'args' => [abf_worker_args]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cancel_job
|
||||||
|
deleted = Resque::Job.destroy(
|
||||||
|
worker_queue_with_priority,
|
||||||
|
worker_queue_class,
|
||||||
|
abf_worker_args
|
||||||
|
)
|
||||||
|
if deleted == 1
|
||||||
|
build_canceled
|
||||||
|
else
|
||||||
|
send_stop_signal
|
||||||
|
end
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def worker_queue_with_priority(queue = nil)
|
||||||
|
queue ||= abf_worker_base_queue
|
||||||
|
queue << '_' << abf_worker_priority if abf_worker_priority.present?
|
||||||
|
queue
|
||||||
|
end
|
||||||
|
|
||||||
|
def worker_queue_class(queue_class = nil)
|
||||||
|
queue_class ||= "AbfWorker::#{abf_worker_base_queue.classify}"
|
||||||
|
queue_class << abf_worker_priority.capitalize
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def send_stop_signal
|
||||||
|
Resque.redis.setex(
|
||||||
|
"#{service_queue}::live-inspector",
|
||||||
|
240, # Data will be removed from Redis after 240 sec.
|
||||||
|
'USR1' # Immediately kill child but don't exit
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def service_queue
|
||||||
|
"abfworker::#{abf_worker_base_queue.gsub(/\_/, '-')}-#{id}"
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
# -*- encoding : utf-8 -*-
|
||||||
|
FactoryGirl.define do
|
||||||
|
factory :project_statistic do
|
||||||
|
association :project, :factory => :project
|
||||||
|
association :arch, :factory => :arch
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,24 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe ProjectStatistic do
|
||||||
|
|
||||||
|
context 'ensures that validations and associations exist' do
|
||||||
|
it { should belong_to(:project) }
|
||||||
|
it { should belong_to(:arch) }
|
||||||
|
|
||||||
|
it { should validate_presence_of(:project_id) }
|
||||||
|
it { should validate_presence_of(:arch_id) }
|
||||||
|
it { should validate_presence_of(:average_build_time) }
|
||||||
|
it { should validate_presence_of(:build_count) }
|
||||||
|
|
||||||
|
it { should_not allow_mass_assignment_of(:project_id) }
|
||||||
|
it { should_not allow_mass_assignment_of(:arch_id) }
|
||||||
|
|
||||||
|
it 'uniqueness of project_id and arch_id' do
|
||||||
|
FactoryGirl.create(:project_statistic)
|
||||||
|
should validate_uniqueness_of(:project_id).scoped_to(:arch_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
Loading…
Reference in New Issue