Commit 6d62617b authored by Sean McGivern's avatar Sean McGivern

Merge branch '28080-system-checks-ee' into 'master'

SystemCheck library (EE part)

See merge request !2019
parents ee367580 c09ad191
---
title: Refactored gitlab:app:check into SystemCheck liberary and improve some checks
merge_request: 9173
author:
# Library to perform System Checks
#
# Every Check is implemented as its own class inherited from SystemCheck::BaseCheck
# Execution coordination and boilerplate output is done by the SystemCheck::SimpleExecutor
#
# This structure decouples checks from Rake tasks and facilitates unit-testing
module SystemCheck
# Executes a bunch of checks for specified component
#
# @param [String] component name of the component relative to the checks being executed
# @param [Array<BaseCheck>] checks classes of corresponding checks to be executed in the same order
def self.run(component, checks = [])
executor = SimpleExecutor.new(component)
checks.each do |check|
executor << check
end
executor.execute
end
end
module SystemCheck
module App
class ActiveUsersCheck < SystemCheck::BaseCheck
set_name 'Active users:'
def multi_check
active_users = User.active.count
if active_users > 0
$stdout.puts active_users.to_s.color(:green)
else
$stdout.puts active_users.to_s.color(:red)
end
end
end
end
end
module SystemCheck
module App
class DatabaseConfigExistsCheck < SystemCheck::BaseCheck
set_name 'Database config exists?'
def check?
database_config_file = Rails.root.join('config', 'database.yml')
File.exist?(database_config_file)
end
def show_error
try_fixing_it(
'Copy config/database.yml.<your db> to config/database.yml',
'Check that the information in config/database.yml is correct'
)
for_more_information(
'doc/install/databases.md',
'http://guides.rubyonrails.org/getting_started.html#configuring-a-database'
)
fix_and_rerun
end
end
end
end
module SystemCheck
module App
class ElasticsearchCheck < SystemCheck::BaseCheck
set_name 'Elasticsearch version 5.1 - 5.3?'
set_skip_reason 'skipped (elasticsearch is disabled)'
set_check_pass -> { "yes (#{self.current_version})" }
set_check_fail -> { "no (#{self.current_version})" }
def self.current_version
@current_version ||= begin
client = Gitlab::Elastic::Client.build(current_application_settings.elasticsearch_config)
Gitlab::VersionInfo.parse(client.info['version']['number'])
end
end
def skip?
!current_application_settings.elasticsearch_indexing?
end
def check?
current_version.major == 5 && (1..3).cover?(version.minor)
end
end
end
end
module SystemCheck
module App
class GitConfigCheck < SystemCheck::BaseCheck
OPTIONS = {
'core.autocrlf' => 'input'
}.freeze
set_name 'Git configured correctly?'
def check?
correct_options = OPTIONS.map do |name, value|
run_command(%W(#{Gitlab.config.git.bin_path} config --global --get #{name})).try(:squish) == value
end
correct_options.all?
end
# Tries to configure git itself
#
# Returns true if all subcommands were successful (according to their exit code)
# Returns false if any or all subcommands failed.
def repair!
return false unless is_gitlab_user?
command_success = OPTIONS.map do |name, value|
system(*%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value}))
end
command_success.all?
end
def show_error
try_fixing_it(
sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global core.autocrlf \"#{OPTIONS['core.autocrlf']}\"")
)
for_more_information(
see_installation_guide_section 'GitLab'
)
end
end
end
end
module SystemCheck
module App
class GitVersionCheck < SystemCheck::BaseCheck
set_name -> { "Git version >= #{self.required_version} ?" }
set_check_pass -> { "yes (#{self.current_version})" }
def self.required_version
@required_version ||= Gitlab::VersionInfo.new(2, 7, 3)
end
def self.current_version
@current_version ||= Gitlab::VersionInfo.parse(run_command(%W(#{Gitlab.config.git.bin_path} --version)))
end
def check?
self.class.current_version.valid? && self.class.required_version <= self.class.current_version
end
def show_error
$stdout.puts "Your git bin path is \"#{Gitlab.config.git.bin_path}\""
try_fixing_it(
"Update your git to a version >= #{self.class.required_version} from #{self.class.current_version}"
)
fix_and_rerun
end
end
end
end
module SystemCheck
module App
class GitlabConfigExistsCheck < SystemCheck::BaseCheck
set_name 'GitLab config exists?'
def check?
gitlab_config_file = Rails.root.join('config', 'gitlab.yml')
File.exist?(gitlab_config_file)
end
def show_error
try_fixing_it(
'Copy config/gitlab.yml.example to config/gitlab.yml',
'Update config/gitlab.yml to match your setup'
)
for_more_information(
see_installation_guide_section 'GitLab'
)
fix_and_rerun
end
end
end
end
module SystemCheck
module App
class GitlabConfigUpToDateCheck < SystemCheck::BaseCheck
set_name 'GitLab config up to date?'
set_skip_reason "can't check because of previous errors"
def skip?
gitlab_config_file = Rails.root.join('config', 'gitlab.yml')
!File.exist?(gitlab_config_file)
end
def check?
# omniauth or ldap could have been deleted from the file
!Gitlab.config['git_host']
end
def show_error
try_fixing_it(
'Back-up your config/gitlab.yml',
'Copy config/gitlab.yml.example to config/gitlab.yml',
'Update config/gitlab.yml to match your setup'
)
for_more_information(
see_installation_guide_section 'GitLab'
)
fix_and_rerun
end
end
end
end
module SystemCheck
module App
class InitScriptExistsCheck < SystemCheck::BaseCheck
set_name 'Init script exists?'
set_skip_reason 'skipped (omnibus-gitlab has no init script)'
def skip?
omnibus_gitlab?
end
def check?
script_path = '/etc/init.d/gitlab'
File.exist?(script_path)
end
def show_error
try_fixing_it(
'Install the init script'
)
for_more_information(
see_installation_guide_section 'Install Init Script'
)
fix_and_rerun
end
end
end
end
module SystemCheck
module App
class InitScriptUpToDateCheck < SystemCheck::BaseCheck
SCRIPT_PATH = '/etc/init.d/gitlab'.freeze
set_name 'Init script up-to-date?'
set_skip_reason 'skipped (omnibus-gitlab has no init script)'
def skip?
omnibus_gitlab?
end
def multi_check
recipe_path = Rails.root.join('lib/support/init.d/', 'gitlab')
unless File.exist?(SCRIPT_PATH)
$stdout.puts "can't check because of previous errors".color(:magenta)
return
end
recipe_content = File.read(recipe_path)
script_content = File.read(SCRIPT_PATH)
if recipe_content == script_content
$stdout.puts 'yes'.color(:green)
else
$stdout.puts 'no'.color(:red)
show_error
end
end
def show_error
try_fixing_it(
'Re-download the init script'
)
for_more_information(
see_installation_guide_section 'Install Init Script'
)
fix_and_rerun
end
end
end
end
module SystemCheck
module App
class LogWritableCheck < SystemCheck::BaseCheck
set_name 'Log directory writable?'
def check?
File.writable?(log_path)
end
def show_error
try_fixing_it(
"sudo chown -R gitlab #{log_path}",
"sudo chmod -R u+rwX #{log_path}"
)
for_more_information(
see_installation_guide_section 'GitLab'
)
fix_and_rerun
end
private
def log_path
Rails.root.join('log')
end
end
end
end
module SystemCheck
module App
class MigrationsAreUpCheck < SystemCheck::BaseCheck
set_name 'All migrations up?'
def check?
migration_status, _ = Gitlab::Popen.popen(%w(bundle exec rake db:migrate:status))
migration_status !~ /down\s+\d{14}/
end
def show_error
try_fixing_it(
sudo_gitlab('bundle exec rake db:migrate RAILS_ENV=production')
)
fix_and_rerun
end
end
end
end
module SystemCheck
module App
class OrphanedGroupMembersCheck < SystemCheck::BaseCheck
set_name 'Database contains orphaned GroupMembers?'
set_check_pass 'no'
set_check_fail 'yes'
def check?
!GroupMember.where('user_id not in (select id from users)').exists?
end
def show_error
try_fixing_it(
'You can delete the orphaned records using something along the lines of:',
sudo_gitlab("bundle exec rails runner -e production 'GroupMember.where(\"user_id NOT IN (SELECT id FROM users)\").delete_all'")
)
end
end
end
end
module SystemCheck
module App
class ProjectsHaveNamespaceCheck < SystemCheck::BaseCheck
set_name 'Projects have namespace:'
set_skip_reason "can't check, you have no projects"
def skip?
!Project.exists?
end
def multi_check
$stdout.puts ''
Project.find_each(batch_size: 100) do |project|
$stdout.print sanitized_message(project)
if project.namespace
$stdout.puts 'yes'.color(:green)
else
$stdout.puts 'no'.color(:red)
show_error
end
end
end
def show_error
try_fixing_it(
"Migrate global projects"
)
for_more_information(
"doc/update/5.4-to-6.0.md in section \"#global-projects\""
)
fix_and_rerun
end
end
end
end
module SystemCheck
module App
class RedisVersionCheck < SystemCheck::BaseCheck
MIN_REDIS_VERSION = '2.8.0'.freeze
set_name "Redis version >= #{MIN_REDIS_VERSION}?"
def check?
redis_version = run_command(%w(redis-cli --version))
redis_version = redis_version.try(:match, /redis-cli (\d+\.\d+\.\d+)/)
redis_version && (Gem::Version.new(redis_version[1]) > Gem::Version.new(MIN_REDIS_VERSION))
end
def show_error
try_fixing_it(
"Update your redis server to a version >= #{MIN_REDIS_VERSION}"
)
for_more_information(
'gitlab-public-wiki/wiki/Trouble-Shooting-Guide in section sidekiq'
)
fix_and_rerun
end
end
end
end
module SystemCheck
module App
class RubyVersionCheck < SystemCheck::BaseCheck
set_name -> { "Ruby version >= #{self.required_version} ?" }
set_check_pass -> { "yes (#{self.current_version})" }
def self.required_version
@required_version ||= Gitlab::VersionInfo.new(2, 3, 3)
end
def self.current_version
@current_version ||= Gitlab::VersionInfo.parse(run_command(%w(ruby --version)))
end
def check?
self.class.current_version.valid? && self.class.required_version <= self.class.current_version
end
def show_error
try_fixing_it(
"Update your ruby to a version >= #{self.class.required_version} from #{self.class.current_version}"
)
fix_and_rerun
end
end
end
end
module SystemCheck
module App
class TmpWritableCheck < SystemCheck::BaseCheck
set_name 'Tmp directory writable?'
def check?
File.writable?(tmp_path)
end
def show_error
try_fixing_it(
"sudo chown -R gitlab #{tmp_path}",
"sudo chmod -R u+rwX #{tmp_path}"
)
for_more_information(
see_installation_guide_section 'GitLab'
)
fix_and_rerun
end
private
def tmp_path
Rails.root.join('tmp')
end
end
end
end
module SystemCheck
module App
class UploadsDirectoryExistsCheck < SystemCheck::BaseCheck
set_name 'Uploads directory exists?'
def check?
File.directory?(Rails.root.join('public/uploads'))
end
def show_error
try_fixing_it(
"sudo -u #{gitlab_user} mkdir #{Rails.root}/public/uploads"
)
for_more_information(
see_installation_guide_section 'GitLab'
)
fix_and_rerun
end
end
end
end
module SystemCheck
module App
class UploadsPathPermissionCheck < SystemCheck::BaseCheck
set_name 'Uploads directory has correct permissions?'
set_skip_reason 'skipped (no uploads folder found)'
def skip?
!File.directory?(rails_uploads_path)
end
def check?
File.stat(uploads_fullpath).mode == 040700
end
def show_error
try_fixing_it(
"sudo chmod 700 #{uploads_fullpath}"
)
for_more_information(
see_installation_guide_section 'GitLab'
)
fix_and_rerun
end
private
def rails_uploads_path
Rails.root.join('public/uploads')
end
def uploads_fullpath
File.realpath(rails_uploads_path)
end
end
end
end
module SystemCheck
module App
class UploadsPathTmpPermissionCheck < SystemCheck::BaseCheck
set_name 'Uploads directory tmp has correct permissions?'
set_skip_reason 'skipped (no tmp uploads folder yet)'
def skip?
!File.directory?(uploads_fullpath) || !Dir.exist?(upload_path_tmp)
end
def check?
# If tmp upload dir has incorrect permissions, assume others do as well
# Verify drwx------ permissions
File.stat(upload_path_tmp).mode == 040700 && File.owned?(upload_path_tmp)
end
def show_error
try_fixing_it(
"sudo chown -R #{gitlab_user} #{uploads_fullpath}",
"sudo find #{uploads_fullpath} -type f -exec chmod 0644 {} \\;",
"sudo find #{uploads_fullpath} -type d -not -path #{uploads_fullpath} -exec chmod 0700 {} \\;"
)
for_more_information(
see_installation_guide_section 'GitLab'
)
fix_and_rerun
end
private
def upload_path_tmp
File.join(uploads_fullpath, 'tmp')
end
def uploads_fullpath
File.realpath(Rails.root.join('public/uploads'))
end
end
end
end
module SystemCheck
# Base class for Checks. You must inherit from here
# and implement the methods below when necessary
class BaseCheck
include ::SystemCheck::Helpers
# Define a custom term for when check passed
#
# @param [String] term used when check passed (default: 'yes')
def self.set_check_pass(term)
@check_pass = term
end
# Define a custom term for when check failed
#
# @param [String] term used when check failed (default: 'no')
def self.set_check_fail(term)
@check_fail = term
end
# Define the name of the SystemCheck that will be displayed during execution
#
# @param [String] name of the check
def self.set_name(name)
@name = name
end
# Define the reason why we skipped the SystemCheck
#
# This is only used if subclass implements `#skip?`
#
# @param [String] reason to be displayed
def self.set_skip_reason(reason)
@skip_reason = reason
end
# Term to be displayed when check passed
#
# @return [String] term when check passed ('yes' if not re-defined in a subclass)
def self.check_pass
call_or_return(@check_pass) || 'yes'
end
## Term to be displayed when check failed
#
# @return [String] term when check failed ('no' if not re-defined in a subclass)
def self.check_fail
call_or_return(@check_fail) || 'no'
end
# Name of the SystemCheck defined by the subclass
#
# @return [String] the name
def self.display_name
call_or_return(@name) || self.name
end
# Skip reason defined by the subclass
#
# @return [String] the reason
def self.skip_reason
call_or_return(@skip_reason) || 'skipped'
end
# Does the check support automatically repair routine?
#
# @return [Boolean] whether check implemented `#repair!` method or not
def can_repair?
self.class.instance_methods(false).include?(:repair!)
end
def can_skip?
self.class.instance_methods(false).include?(:skip?)
end
def is_multi_check?
self.class.instance_methods(false).include?(:multi_check)
end
# Execute the check routine
#
# This is where you should implement the main logic that will return
# a boolean at the end
#
# You should not print any output to STDOUT here, use the specific methods instead
#
# @return [Boolean] whether check passed or failed
def check?
raise NotImplementedError
end
# Execute a custom check that cover multiple unities
#
# When using multi_check you have to provide the output yourself
def multi_check
raise NotImplementedError
end
# Prints troubleshooting instructions
#
# This is where you should print detailed information for any error found during #check?
#
# You may use helper methods to help format the output:
#
# @see #try_fixing_it
# @see #fix_and_rerun
# @see #for_more_infromation
def show_error
raise NotImplementedError
end
# When implemented by a subclass, will attempt to fix the issue automatically
def repair!
raise NotImplementedError
end
# When implemented by a subclass, will evaluate whether check should be skipped or not
#
# @return [Boolean] whether or not this check should be skipped
def skip?
raise NotImplementedError
end
def self.call_or_return(input)
input.respond_to?(:call) ? input.call : input
end
private_class_method :call_or_return
end
end
require 'tasks/gitlab/task_helpers'
module SystemCheck
module Helpers
include ::Gitlab::TaskHelpers
# Display a message telling to fix and rerun the checks
def fix_and_rerun
$stdout.puts ' Please fix the error above and rerun the checks.'.color(:red)
end
# Display a formatted list of references (documentation or links) where to find more information
#
# @param [Array<String>] sources one or more references (documentation or links)
def for_more_information(*sources)
$stdout.puts ' For more information see:'.color(:blue)
sources.each do |source|
$stdout.puts " #{source}"
end
end
def see_installation_guide_section(section)
"doc/install/installation.md in section \"#{section}\""
end
# @deprecated This will no longer be used when all checks were executed using SystemCheck
def finished_checking(component)
$stdout.puts ''
$stdout.puts "Checking #{component.color(:yellow)} ... #{'Finished'.color(:green)}"
$stdout.puts ''
end
# @deprecated This will no longer be used when all checks were executed using SystemCheck
def start_checking(component)
$stdout.puts "Checking #{component.color(:yellow)} ..."
$stdout.puts ''
end
# Display a formatted list of instructions on how to fix the issue identified by the #check?
#
# @param [Array<String>] steps one or short sentences with help how to fix the issue
def try_fixing_it(*steps)
steps = steps.shift if steps.first.is_a?(Array)
$stdout.puts ' Try fixing it:'.color(:blue)
steps.each do |step|
$stdout.puts " #{step}"
end
end
def sanitized_message(project)
if should_sanitize?
"#{project.namespace_id.to_s.color(:yellow)}/#{project.id.to_s.color(:yellow)} ... "
else
"#{project.name_with_namespace.color(:yellow)} ... "
end
end
def should_sanitize?
if ENV['SANITIZE'] == 'true'
true
else
false
end
end
def omnibus_gitlab?
Dir.pwd == '/opt/gitlab/embedded/service/gitlab-rails'
end
def sudo_gitlab(command)
"sudo -u #{gitlab_user} -H #{command}"
end
end
end
module SystemCheck
# Simple Executor is current default executor for GitLab
# It is a simple port from display logic in the old check.rake
#
# There is no concurrency level and the output is progressively
# printed into the STDOUT
#
# @attr_reader [Array<BaseCheck>] checks classes of corresponding checks to be executed in the same order
# @attr_reader [String] component name of the component relative to the checks being executed
class SimpleExecutor
attr_reader :checks
attr_reader :component
# @param [String] component name of the component relative to the checks being executed
def initialize(component)
raise ArgumentError unless component.is_a? String
@component = component
@checks = Set.new
end
# Add a check to be executed
#
# @param [BaseCheck] check class
def <<(check)
raise ArgumentError unless check < BaseCheck
@checks << check
end
# Executes defined checks in the specified order and outputs confirmation or error information
def execute
start_checking(component)
@checks.each do |check|
run_check(check)
end
finished_checking(component)
end
# Executes a single check
#
# @param [SystemCheck::BaseCheck] check_klass
def run_check(check_klass)
$stdout.print "#{check_klass.display_name} ... "
check = check_klass.new
# When implements skip method, we run it first, and if true, skip the check
if check.can_skip? && check.skip?
$stdout.puts check_klass.skip_reason.color(:magenta)
return
end
# When implements a multi check, we don't control the output
if check.is_multi_check?
check.multi_check
return
end
if check.check?
$stdout.puts check_klass.check_pass.color(:green)
else
$stdout.puts check_klass.check_fail.color(:red)
if check.can_repair?
$stdout.print 'Trying to fix error automatically. ...'
if check.repair!
$stdout.puts 'Success'.color(:green)
return
else
$stdout.puts 'Failed'.color(:red)
end
end
check.show_error
end
end
private
# Prints header content for the series of checks to be executed for this component
#
# @param [String] component name of the component relative to the checks being executed
def start_checking(component)
$stdout.puts "Checking #{component.color(:yellow)} ..."
$stdout.puts ''
end
# Prints footer content for the series of checks executed for this component
#
# @param [String] component name of the component relative to the checks being executed
def finished_checking(component)
$stdout.puts ''
$stdout.puts "Checking #{component.color(:yellow)} ... #{'Finished'.color(:green)}"
$stdout.puts ''
end
end
end
This diff is collapsed.
...@@ -98,34 +98,30 @@ module Gitlab ...@@ -98,34 +98,30 @@ module Gitlab
end end
end end
def gitlab_user
Gitlab.config.gitlab.user
end
def is_gitlab_user?
return @is_gitlab_user unless @is_gitlab_user.nil?
current_user = run_command(%w(whoami)).chomp
@is_gitlab_user = current_user == gitlab_user
end
def warn_user_is_not_gitlab def warn_user_is_not_gitlab
unless @warned_user_not_gitlab return if @warned_user_not_gitlab
gitlab_user = Gitlab.config.gitlab.user
unless is_gitlab_user?
current_user = run_command(%w(whoami)).chomp current_user = run_command(%w(whoami)).chomp
unless current_user == gitlab_user
puts " Warning ".color(:black).background(:yellow)
puts " You are running as user #{current_user.color(:magenta)}, we hope you know what you are doing."
puts " Things may work\/fail for the wrong reasons."
puts " For correct results you should run this as user #{gitlab_user.color(:magenta)}."
puts ""
end
@warned_user_not_gitlab = true
end
end
# Tries to configure git itself puts " Warning ".color(:black).background(:yellow)
# puts " You are running as user #{current_user.color(:magenta)}, we hope you know what you are doing."
# Returns true if all subcommands were successfull (according to their exit code) puts " Things may work\/fail for the wrong reasons."
# Returns false if any or all subcommands failed. puts " For correct results you should run this as user #{gitlab_user.color(:magenta)}."
def auto_fix_git_config(options) puts ""
if !@warned_user_not_gitlab
command_success = options.map do |name, value|
system(*%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value}))
end
command_success.all? @warned_user_not_gitlab = true
else
false
end end
end end
......
require 'spec_helper' require 'spec_helper'
require 'rainbow/ext/string'
describe 'seed production settings', lib: true do describe 'seed production settings', lib: true do
include StubENV include StubENV
......
require 'spec_helper'
require 'rake_helper'
describe SystemCheck::SimpleExecutor, lib: true do
class SimpleCheck < SystemCheck::BaseCheck
set_name 'my simple check'
def check?
true
end
end
class OtherCheck < SystemCheck::BaseCheck
set_name 'other check'
def check?
false
end
def show_error
$stdout.puts 'this is an error text'
end
end
class SkipCheck < SystemCheck::BaseCheck
set_name 'skip check'
set_skip_reason 'this is a skip reason'
def skip?
true
end
def check?
raise 'should not execute this'
end
end
class MultiCheck < SystemCheck::BaseCheck
set_name 'multi check'
def multi_check
$stdout.puts 'this is a multi output check'
end
def check?
raise 'should not execute this'
end
end
class SkipMultiCheck < SystemCheck::BaseCheck
set_name 'skip multi check'
def skip?
true
end
def multi_check
raise 'should not execute this'
end
end
class RepairCheck < SystemCheck::BaseCheck
set_name 'repair check'
def check?
false
end
def repair!
true
end
def show_error
$stdout.puts 'this is an error message'
end
end
describe '#component' do
it 'returns stored component name' do
expect(subject.component).to eq('Test')
end
end
describe '#checks' do
before do
subject << SimpleCheck
end
it 'returns a set of classes' do
expect(subject.checks).to include(SimpleCheck)
end
end
describe '#<<' do
before do
subject << SimpleCheck
end
it 'appends a new check to the Set' do
subject << OtherCheck
stored_checks = subject.checks.to_a
expect(stored_checks.first).to eq(SimpleCheck)
expect(stored_checks.last).to eq(OtherCheck)
end
it 'inserts unique itens only' do
subject << SimpleCheck
expect(subject.checks.size).to eq(1)
end
end
subject { described_class.new('Test') }
describe '#execute' do
before do
silence_output
subject << SimpleCheck
subject << OtherCheck
end
it 'runs included checks' do
expect(subject).to receive(:run_check).with(SimpleCheck)
expect(subject).to receive(:run_check).with(OtherCheck)
subject.execute
end
end
describe '#run_check' do
it 'prints check name' do
expect(SimpleCheck).to receive(:display_name).and_call_original
expect { subject.run_check(SimpleCheck) }.to output(/my simple check/).to_stdout
end
context 'when check pass' do
it 'prints yes' do
expect_any_instance_of(SimpleCheck).to receive(:check?).and_call_original
expect { subject.run_check(SimpleCheck) }.to output(/ \.\.\. yes/).to_stdout
end
end
context 'when check fails' do
it 'prints no' do
expect_any_instance_of(OtherCheck).to receive(:check?).and_call_original
expect { subject.run_check(OtherCheck) }.to output(/ \.\.\. no/).to_stdout
end
it 'displays error message from #show_error' do
expect_any_instance_of(OtherCheck).to receive(:show_error).and_call_original
expect { subject.run_check(OtherCheck) }.to output(/this is an error text/).to_stdout
end
context 'when check implements #repair!' do
it 'executes #repair!' do
expect_any_instance_of(RepairCheck).to receive(:repair!)
subject.run_check(RepairCheck)
end
context 'when repair succeeds' do
it 'does not execute #show_error' do
expect_any_instance_of(RepairCheck).to receive(:repair!).and_call_original
expect_any_instance_of(RepairCheck).not_to receive(:show_error)
subject.run_check(RepairCheck)
end
end
context 'when repair fails' do
it 'does not execute #show_error' do
expect_any_instance_of(RepairCheck).to receive(:repair!) { false }
expect_any_instance_of(RepairCheck).to receive(:show_error)
subject.run_check(RepairCheck)
end
end
end
end
context 'when check implements skip?' do
it 'executes #skip? method' do
expect_any_instance_of(SkipCheck).to receive(:skip?).and_call_original
subject.run_check(SkipCheck)
end
it 'displays #skip_reason' do
expect { subject.run_check(SkipCheck) }.to output(/this is a skip reason/).to_stdout
end
it 'does not execute #check when #skip? is true' do
expect_any_instance_of(SkipCheck).not_to receive(:check?)
subject.run_check(SkipCheck)
end
end
context 'when implements a #multi_check' do
it 'executes #multi_check method' do
expect_any_instance_of(MultiCheck).to receive(:multi_check)
subject.run_check(MultiCheck)
end
it 'does not execute #check method' do
expect_any_instance_of(MultiCheck).not_to receive(:check)
subject.run_check(MultiCheck)
end
context 'when check implements #skip?' do
it 'executes #skip? method' do
expect_any_instance_of(SkipMultiCheck).to receive(:skip?).and_call_original
subject.run_check(SkipMultiCheck)
end
end
end
end
end
require 'spec_helper'
require 'rake_helper'
describe SystemCheck, lib: true do
class SimpleCheck < SystemCheck::BaseCheck
def check?
true
end
end
class OtherCheck < SystemCheck::BaseCheck
def check?
false
end
end
before do
silence_output
end
describe '.run' do
subject { SystemCheck }
it 'detects execution of SimpleCheck' do
is_expected.to execute_check(SimpleCheck)
subject.run('Test', [SimpleCheck])
end
it 'detects exclusion of OtherCheck in execution' do
is_expected.not_to execute_check(OtherCheck)
subject.run('Test', [SimpleCheck])
end
end
end
...@@ -26,6 +26,9 @@ if ENV['CI'] && !ENV['NO_KNAPSACK'] ...@@ -26,6 +26,9 @@ if ENV['CI'] && !ENV['NO_KNAPSACK']
Knapsack::Adapters::RSpecAdapter.bind Knapsack::Adapters::RSpecAdapter.bind
end end
# require rainbow gem String monkeypatch, so we can test SystemChecks
require 'rainbow/ext/string'
# Requires supporting ruby files with custom matchers and macros, etc, # Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories. # in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
......
RSpec::Matchers.define :execute_check do |expected|
match do |actual|
expect(actual).to eq(SystemCheck)
expect(actual).to receive(:run) do |*args|
expect(args[1]).to include(expected)
end
end
match_when_negated do |actual|
expect(actual).to eq(SystemCheck)
expect(actual).to receive(:run) do |*args|
expect(args[1]).not_to include(expected)
end
end
failure_message do |actual|
'This matcher must be used with SystemCheck' unless actual == SystemCheck
end
failure_message_when_negated do |actual|
'This matcher must be used with SystemCheck' unless actual == SystemCheck
end
end
...@@ -7,4 +7,9 @@ module RakeHelpers ...@@ -7,4 +7,9 @@ module RakeHelpers
def stub_warn_user_is_not_gitlab def stub_warn_user_is_not_gitlab
allow_any_instance_of(Object).to receive(:warn_user_is_not_gitlab) allow_any_instance_of(Object).to receive(:warn_user_is_not_gitlab)
end end
def silence_output
allow($stdout).to receive(:puts)
allow($stdout).to receive(:print)
end
end end
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