Commit 3c77018e authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 088c5074 2e85fc86
import Vue from 'vue';
import GitlabVersionCheck from '~/vue_shared/components/gitlab_version_check.vue';
const mountGitlabVersionCheck = (el) => {
const { size } = el.dataset;
return new Vue({
el,
render(createElement) {
return createElement(GitlabVersionCheck, {
props: {
size,
},
});
},
});
};
export default () =>
[...document.querySelectorAll('.js-gitlab-version-check')].map(mountGitlabVersionCheck);
import $ from 'jquery';
import docs from '~/docs/docs_bundle';
import VersionCheckImage from '~/version_check_image';
import initGitlabVersionCheck from '~/gitlab_version_check';
docs();
VersionCheckImage.bindErrorEvent($('img.js-version-status-badge'));
initGitlabVersionCheck();
export default class VersionCheckImage {
static bindErrorEvent(imageElement) {
// eslint-disable-next-line @gitlab/no-global-event-off
imageElement.off('error').on('error', () => imageElement.hide());
}
}
<script>
import { GlBadge } from '@gitlab/ui';
import { s__ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
const STATUS_TYPES = {
SUCCESS: 'success',
WARNING: 'warning',
DANGER: 'danger',
};
export default {
name: 'GitlabVersionCheck',
components: {
GlBadge,
},
props: {
size: {
type: String,
required: false,
default: 'md',
},
},
data() {
return {
status: null,
};
},
computed: {
title() {
if (this.status === STATUS_TYPES.SUCCESS) {
return s__('VersionCheck|Up to date');
} else if (this.status === STATUS_TYPES.WARNING) {
return s__('VersionCheck|Update available');
} else if (this.status === STATUS_TYPES.DANGER) {
return s__('VersionCheck|Update ASAP');
}
return null;
},
},
created() {
this.checkGitlabVersion();
},
methods: {
checkGitlabVersion() {
axios
.get('/admin/version_check.json')
.then((res) => {
if (res.data) {
this.status = res.data.severity;
}
})
.catch(() => {
// Silently fail
this.status = null;
});
},
},
};
</script>
<template>
<gl-badge v-if="status" class="version-check-badge" :variant="status" :size="size">{{
title
}}</gl-badge>
</template>
......@@ -3,6 +3,8 @@
module Types
module Packages
class PackageDetailsType < PackageType
include ::PackagesHelper
graphql_name 'PackageDetailsType'
description 'Represents a package details in the Package Registry. Note that this type is in beta and susceptible to changes'
authorize :read_package
......@@ -21,6 +23,15 @@ module Types
description: 'Pipelines that built the package.',
deprecated: { reason: 'Due to scalability concerns, this field is going to be removed', milestone: '14.6' }
field :composer_config_repository_url, GraphQL::Types::String, null: true, description: 'Url of the Composer setup endpoint.'
field :composer_url, GraphQL::Types::String, null: true, description: 'Url of the Composer endpoint.'
field :conan_url, GraphQL::Types::String, null: true, description: 'Url of the Conan project endpoint.'
field :maven_url, GraphQL::Types::String, null: true, description: 'Url of the Maven project endpoint.'
field :npm_url, GraphQL::Types::String, null: true, description: 'Url of the NPM project endpoint.'
field :nuget_url, GraphQL::Types::String, null: true, description: 'Url of the Nuget project endpoint.'
field :pypi_setup_url, GraphQL::Types::String, null: true, description: 'Url of the PyPi project setup endpoint.'
field :pypi_url, GraphQL::Types::String, null: true, description: 'Url of the PyPi project endpoint.'
def versions
object.versions
end
......@@ -32,6 +43,38 @@ module Types
object.package_files
end
end
def composer_config_repository_url
composer_config_repository_name(object.project.group&.id)
end
def composer_url
composer_registry_url(object.project.group&.id)
end
def conan_url
package_registry_project_url(object.project.id, :conan)
end
def maven_url
package_registry_project_url(object.project.id, :maven)
end
def npm_url
package_registry_project_url(object.project.id, :npm)
end
def nuget_url
nuget_package_registry_url(object.project.id)
end
def pypi_setup_url
package_registry_project_url(object.project.id, :pypi)
end
def pypi_url
pypi_registry_url(object.project.id)
end
end
end
end
# frozen_string_literal: true
module PackagesHelper
include ::API::Helpers::RelatedResourcesHelpers
def package_sort_path(options = {})
"#{request.path}?#{options.to_param}"
end
......
# frozen_string_literal: true
module VersionCheckHelper
def version_status_badge
return unless Rails.env.production?
return unless Gitlab::CurrentSettings.version_check_enabled
return if User.single_user&.requires_usage_stats_consent?
def show_version_check?
return false unless Gitlab::CurrentSettings.version_check_enabled
return false if User.single_user&.requires_usage_stats_consent?
image_tag VersionCheck.image_url, class: 'js-version-status-badge'
current_user&.can_read_all_resources?
end
def link_to_version
......
......@@ -118,9 +118,9 @@
.gl-card-body
%h4
= s_('AdminArea|Components')
- if Gitlab::CurrentSettings.version_check_enabled
- if show_version_check?
.float-right
= version_status_badge
.js-gitlab-version-check{ data: { "size": "lg" } }
= link_to(sprite_icon('question'), "https://gitlab.com/gitlab-org/gitlab/-/blob/master/CHANGELOG.md", class: 'gl-ml-2', target: '_blank', rel: 'noopener noreferrer')
%p
= link_to _('GitLab'), general_admin_application_settings_path
......
......@@ -4,12 +4,15 @@
= markdown_field(Gitlab::CurrentSettings.current_application_settings, :help_page_text)
%hr
%h1
= default_brand_title
- if user_signed_in?
%span= link_to_version
= version_status_badge
%hr
.gl-display-flex.gl-align-items-flex-end
%h1.gl-mt-5.gl-mb-3
= default_brand_title
- if user_signed_in?
%span= link_to_version
- if show_version_check?
%span.gl-mt-5.gl-mb-3.gl-ml-3
.js-gitlab-version-check{ data: { "size": "lg" } }
%hr
- unless Gitlab::CurrentSettings.help_page_hide_commercial_content?
%p.slead
......
......@@ -12701,15 +12701,23 @@ Represents a package details in the Package Registry. Note that this type is in
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="packagedetailstypecandestroy"></a>`canDestroy` | [`Boolean!`](#boolean) | Whether the user can destroy the package. |
| <a id="packagedetailstypecomposerconfigrepositoryurl"></a>`composerConfigRepositoryUrl` | [`String`](#string) | Url of the Composer setup endpoint. |
| <a id="packagedetailstypecomposerurl"></a>`composerUrl` | [`String`](#string) | Url of the Composer endpoint. |
| <a id="packagedetailstypeconanurl"></a>`conanUrl` | [`String`](#string) | Url of the Conan project endpoint. |
| <a id="packagedetailstypecreatedat"></a>`createdAt` | [`Time!`](#time) | Date of creation. |
| <a id="packagedetailstypedependencylinks"></a>`dependencyLinks` | [`PackageDependencyLinkConnection`](#packagedependencylinkconnection) | Dependency link. (see [Connections](#connections)) |
| <a id="packagedetailstypeid"></a>`id` | [`PackagesPackageID!`](#packagespackageid) | ID of the package. |
| <a id="packagedetailstypemavenurl"></a>`mavenUrl` | [`String`](#string) | Url of the Maven project endpoint. |
| <a id="packagedetailstypemetadata"></a>`metadata` | [`PackageMetadata`](#packagemetadata) | Package metadata. |
| <a id="packagedetailstypename"></a>`name` | [`String!`](#string) | Name of the package. |
| <a id="packagedetailstypenpmurl"></a>`npmUrl` | [`String`](#string) | Url of the NPM project endpoint. |
| <a id="packagedetailstypenugeturl"></a>`nugetUrl` | [`String`](#string) | Url of the Nuget project endpoint. |
| <a id="packagedetailstypepackagefiles"></a>`packageFiles` | [`PackageFileConnection`](#packagefileconnection) | Package files. (see [Connections](#connections)) |
| <a id="packagedetailstypepackagetype"></a>`packageType` | [`PackageTypeEnum!`](#packagetypeenum) | Package type. |
| <a id="packagedetailstypepipelines"></a>`pipelines` **{warning-solid}** | [`PipelineConnection`](#pipelineconnection) | **Deprecated** in 14.6. Due to scalability concerns, this field is going to be removed. |
| <a id="packagedetailstypeproject"></a>`project` | [`Project!`](#project) | Project where the package is stored. |
| <a id="packagedetailstypepypisetupurl"></a>`pypiSetupUrl` | [`String`](#string) | Url of the PyPi project setup endpoint. |
| <a id="packagedetailstypepypiurl"></a>`pypiUrl` | [`String`](#string) | Url of the PyPi project endpoint. |
| <a id="packagedetailstypestatus"></a>`status` | [`PackageStatus!`](#packagestatus) | Package status. |
| <a id="packagedetailstypetags"></a>`tags` | [`PackageTagConnection`](#packagetagconnection) | Package tags. (see [Connections](#connections)) |
| <a id="packagedetailstypeupdatedat"></a>`updatedAt` | [`Time!`](#time) | Date of most recent update. |
import { shouldQrtlyReconciliationMount } from 'ee/billings/qrtly_reconciliation';
import initGitlabVersionCheck from '~/gitlab_version_check';
shouldQrtlyReconciliationMount();
initGitlabVersionCheck();
......@@ -2,41 +2,6 @@
namespace :gitlab do
namespace :gitaly do
desc 'Installs gitaly for running tests within gitlab-development-kit'
task :test_install, [:dir, :storage_path, :repo] => :gitlab_environment do |t, args|
inside_gdk = Rails.env.test? && File.exist?(Rails.root.join('../GDK_ROOT'))
if ENV['FORCE_GITALY_INSTALL'] || !inside_gdk
Rake::Task["gitlab:gitaly:install"].invoke(*args)
next
end
gdk_gitaly_dir = ENV.fetch('GDK_GITALY', Rails.root.join('../gitaly'))
# Our test setup expects a git repo, so clone rather than copy
clone_repo(gdk_gitaly_dir, args.dir, clone_opts: %w[--depth 1]) unless Dir.exist?(args.dir)
# We assume the GDK gitaly already compiled binaries
build_dir = File.join(gdk_gitaly_dir, '_build')
FileUtils.cp_r(build_dir, args.dir)
# We assume the GDK gitaly already ran bundle install
bundle_dir = File.join(gdk_gitaly_dir, 'ruby', '.bundle')
FileUtils.cp_r(bundle_dir, File.join(args.dir, 'ruby'))
# For completeness we copy this for gitaly's make target
ruby_bundle_file = File.join(gdk_gitaly_dir, '.ruby-bundle')
FileUtils.cp_r(ruby_bundle_file, args.dir)
gitaly_binary = File.join(build_dir, 'bin', 'gitaly')
warn_gitaly_out_of_date!(gitaly_binary, Gitlab::GitalyClient.expected_server_version)
rescue Errno::ENOENT => e
puts "Could not copy files, did you run `gdk update`? Error: #{e.message}"
raise
end
desc 'GitLab | Gitaly | Clone and checkout gitaly'
task :clone, [:dir, :storage_path, :repo] => :gitlab_environment do |t, args|
warn_user_is_not_gitlab
......@@ -60,9 +25,6 @@ Usage: rake "gitlab:gitaly:install[/installation/dir,/storage/path]")
storage_paths = { 'default' => args.storage_path }
Gitlab::SetupHelper::Gitaly.create_configuration(args.dir, storage_paths)
# In CI we run scripts/gitaly-test-build
next if ENV['CI'].present?
Dir.chdir(args.dir) do
Bundler.with_original_env do
env = { "RUBYOPT" => nil, "BUNDLE_GEMFILE" => nil }
......
......@@ -16,14 +16,6 @@ class VersionCheck
{ "REFERER": Gitlab.config.gitlab.url }
end
# This is temporary and will be removed when the new UI is hooked up
# to the version_check.json endpoint.
def self.image_url
encoded_data = Base64.urlsafe_encode64(data.to_json)
"#{host}/check.svg?gitlab_info=#{encoded_data}"
end
def self.url
encoded_data = Base64.urlsafe_encode64(data.to_json)
......
......@@ -39116,6 +39116,15 @@ msgstr ""
msgid "Version %{versionNumber} (latest)"
msgstr ""
msgid "VersionCheck|Up to date"
msgstr ""
msgid "VersionCheck|Update ASAP"
msgstr ""
msgid "VersionCheck|Update available"
msgstr ""
msgid "View Documentation"
msgstr ""
......
......@@ -9,6 +9,7 @@ module QA
extend self
attr_writer :personal_access_token, :admin_personal_access_token
attr_accessor :dry_run
ENV_VARIABLES = Gitlab::QA::Runtime::Env::ENV_VARIABLES
......
......@@ -30,6 +30,13 @@ module QA
Runtime::Scenario.define(opt.name, value)
end
next
elsif opt.name == :count_examples_only
parser.on(opt.arg, opt.desc) do |value|
QA::Runtime::Env.dry_run = true
Runtime::Scenario.define(opt.name, value)
end
next
end
......
......@@ -13,6 +13,7 @@ module QA
'Specify FEATURE_FLAGS as comma-separated flag=state pairs, e.g., "flag1=enabled,flag2=disabled"'
attribute :parallel, '--parallel', 'Execute tests in parallel'
attribute :loop, '--loop', 'Execute test repeatedly'
attribute :count_examples_only, '--count-examples-only', 'Return the number of examples without running them'
end
end
end
......@@ -40,7 +40,8 @@ module QA
##
# Perform before hooks, which are different for CE and EE
#
Runtime::Release.perform_before_hooks
Runtime::Release.perform_before_hooks unless Runtime::Env.dry_run
Runtime::Feature.enable(options[:enable_feature]) if options.key?(:enable_feature)
Runtime::Feature.disable(options[:disable_feature]) if options.key?(:disable_feature) && (@feature_enabled = Runtime::Feature.enabled?(options[:disable_feature]))
......
......@@ -9,6 +9,7 @@ module QA
attr_accessor :tty, :tags, :options
DEFAULT_TEST_PATH_ARGS = ['--', File.expand_path('./features', __dir__)].freeze
DEFAULT_STD_ARGS = [$stderr, $stdout].freeze
def initialize
@tty = false
......@@ -68,8 +69,15 @@ module QA
ParallelRunner.run(args.flatten)
elsif Runtime::Scenario.attributes[:loop]
LoopRunner.run(args.flatten)
elsif Runtime::Scenario.attributes[:count_examples_only]
args.unshift('--dry-run')
out = StringIO.new
RSpec::Core::Runner.run(args.flatten, $stderr, out).tap do |status|
abort if status.nonzero?
end
$stdout.puts out.string.match(/(\d+) examples,/)[1]
else
RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status|
RSpec::Core::Runner.run(args.flatten, *DEFAULT_STD_ARGS).tap do |status|
abort if status.nonzero?
end
end
......
......@@ -2,7 +2,7 @@
RSpec.describe QA::Scenario::Test::Integration::Github do
describe '#perform' do
let(:env) { spy('Runtime::Env', knapsack?: false) }
let(:env) { spy('Runtime::Env', knapsack?: false, dry_run: false) }
before do
stub_const('QA::Runtime::Env', env)
......
......@@ -12,7 +12,7 @@ QaDeprecationToolkitEnv.configure!
Knapsack::Adapters::RSpecAdapter.bind if QA::Runtime::Env.knapsack?
QA::Runtime::Browser.configure!
QA::Runtime::Browser.configure! unless QA::Runtime::Env.dry_run
QA::Runtime::AllureReport.configure!
QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes)
......
......@@ -28,6 +28,26 @@ RSpec.describe QA::Specs::Runner do
end
end
context 'when count_examples_only is set as an option' do
let(:out) { StringIO.new }
before do
QA::Runtime::Scenario.define(:count_examples_only, true)
out.string = '22 examples,'
allow(StringIO).to receive(:new).and_return(out)
end
it 'sets the `--dry-run` flag' do
expect_rspec_runner_arguments(['--dry-run', '--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS], [$stderr, anything])
subject.perform
end
after do
QA::Runtime::Scenario.attributes.delete(:count_examples_only)
end
end
context 'when tags are set' do
subject { described_class.new.tap { |runner| runner.tags = %i[orchestrated github] } }
......@@ -158,10 +178,10 @@ RSpec.describe QA::Specs::Runner do
end
end
def expect_rspec_runner_arguments(arguments)
def expect_rspec_runner_arguments(arguments, std_arguments = described_class::DEFAULT_STD_ARGS)
expect(RSpec::Core::Runner).to receive(:run)
.with(arguments, $stderr, $stdout)
.and_return(0)
.with(arguments, *std_arguments)
.and_return(0)
end
end
end
......@@ -13,8 +13,6 @@ class GitalyTestBuild
include GitalySetup
def run
set_bundler_config
# If we have the binaries from the cache, we can skip building them again
if File.exist?(tmp_tests_gitaly_bin_dir)
GitalySetup::LOGGER.debug "Gitaly binary already built. Skip building...\n"
......
......@@ -9,17 +9,8 @@ class GitalyTestSpawn
include GitalySetup
def run
set_bundler_config
install_gitaly_gems if ENV['CI']
check_gitaly_config!
# # Uncomment line below to see all gitaly logs merged into CI trace
# spawn('sleep 1; tail -f log/gitaly-test.log')
# In local development this pid file is used by rspec.
IO.write(File.expand_path('../tmp/tests/gitaly.pid', __dir__), start_gitaly)
IO.write(File.expand_path('../tmp/tests/gitaly2.pid', __dir__), start_gitaly2)
IO.write(File.expand_path('../tmp/tests/praefect.pid', __dir__), start_praefect)
install_gitaly_gems
spawn_gitaly
end
end
......
......@@ -28,21 +28,20 @@ RSpec.describe 'Help Pages' do
end
end
context 'in a production environment with version check enabled' do
describe 'with version check enabled' do
let_it_be(:user) { create(:user) }
before do
stub_application_setting(version_check_enabled: true)
allow(User).to receive(:single_user).and_return(double(user, requires_usage_stats_consent?: false))
allow(user).to receive(:can_read_all_resources?).and_return(true)
stub_rails_env('production')
allow(VersionCheck).to receive(:image_url).and_return('/version-check-url')
sign_in(create(:user))
sign_in(user)
visit help_path
end
it 'has a version check image' do
# Check `data-src` due to lazy image loading
expect(find('.js-version-status-badge', visible: false)['data-src'])
.to end_with('/version-check-url')
it 'renders the version check badge' do
expect(page).to have_selector('.js-gitlab-version-check')
end
end
......
......@@ -149,6 +149,30 @@
}
}
}
},
"npmUrl": {
"type": "string"
},
"mavenUrl": {
"type": "string"
},
"conanUrl": {
"type": "string"
},
"nugetUrl": {
"type": "string"
},
"pypiUrl": {
"type": "string"
},
"pypiSetupUrl": {
"type": "string"
},
"composerUrl": {
"type": "string"
},
"composerConfigRepositoryUrl": {
"type": "string"
}
}
}
import $ from 'jquery';
import ClassSpecHelper from 'helpers/class_spec_helper';
import VersionCheckImage from '~/version_check_image';
describe('VersionCheckImage', () => {
let testContext;
beforeEach(() => {
testContext = {};
});
describe('bindErrorEvent', () => {
ClassSpecHelper.itShouldBeAStaticMethod(VersionCheckImage, 'bindErrorEvent');
beforeEach(() => {
testContext.imageElement = $('<div></div>');
});
it('registers an error event', () => {
jest.spyOn($.prototype, 'on').mockImplementation(() => {});
// eslint-disable-next-line func-names
jest.spyOn($.prototype, 'off').mockImplementation(function () {
return this;
});
VersionCheckImage.bindErrorEvent(testContext.imageElement);
expect($.prototype.off).toHaveBeenCalledWith('error');
expect($.prototype.on).toHaveBeenCalledWith('error', expect.any(Function));
});
it('hides the imageElement on error', () => {
jest.spyOn($.prototype, 'hide').mockImplementation(() => {});
VersionCheckImage.bindErrorEvent(testContext.imageElement);
testContext.imageElement.trigger('error');
expect($.prototype.hide).toHaveBeenCalled();
});
});
});
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import flushPromises from 'helpers/flush_promises';
import axios from '~/lib/utils/axios_utils';
import GitlabVersionCheck from '~/vue_shared/components/gitlab_version_check.vue';
describe('GitlabVersionCheck', () => {
let wrapper;
let mock;
const defaultResponse = {
code: 200,
res: { severity: 'success' },
};
const createComponent = (mockResponse) => {
const response = {
...defaultResponse,
...mockResponse,
};
mock = new MockAdapter(axios);
mock.onGet().replyOnce(response.code, response.res);
wrapper = shallowMount(GitlabVersionCheck);
};
afterEach(() => {
wrapper.destroy();
mock.restore();
});
const findGlBadge = () => wrapper.findComponent(GlBadge);
describe('template', () => {
describe.each`
description | mockResponse | renders
${'successful but null'} | ${{ code: 200, res: null }} | ${false}
${'successful and valid'} | ${{ code: 200, res: { severity: 'success' } }} | ${true}
${'an error'} | ${{ code: 500, res: null }} | ${false}
`('version_check.json response', ({ description, mockResponse, renders }) => {
describe(`is ${description}`, () => {
beforeEach(async () => {
createComponent(mockResponse);
await flushPromises(); // Ensure we wrap up the axios call
});
it(`does${renders ? '' : ' not'} render GlBadge`, () => {
expect(findGlBadge().exists()).toBe(renders);
});
});
});
describe.each`
mockResponse | expectedUI
${{ code: 200, res: { severity: 'success' } }} | ${{ title: 'Up to date', variant: 'success' }}
${{ code: 200, res: { severity: 'warning' } }} | ${{ title: 'Update available', variant: 'warning' }}
${{ code: 200, res: { severity: 'danger' } }} | ${{ title: 'Update ASAP', variant: 'danger' }}
`('badge ui', ({ mockResponse, expectedUI }) => {
describe(`when response is ${mockResponse.res.severity}`, () => {
beforeEach(async () => {
createComponent(mockResponse);
await flushPromises(); // Ensure we wrap up the axios call
});
it(`title is ${expectedUI.title}`, () => {
expect(findGlBadge().text()).toBe(expectedUI.title);
});
it(`variant is ${expectedUI.variant}`, () => {
expect(findGlBadge().attributes('variant')).toBe(expectedUI.variant);
});
});
});
});
});
......@@ -5,7 +5,10 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['PackageDetailsType'] do
it 'includes all the package fields' do
expected_fields = %w[
id name version created_at updated_at package_type tags project pipelines versions package_files dependency_links
id name version created_at updated_at package_type tags project
pipelines versions package_files dependency_links
npm_url maven_url conan_url nuget_url pypi_url pypi_setup_url
composer_url composer_config_repository_url
]
expect(described_class).to include_graphql_fields(*expected_fields)
......
......@@ -3,33 +3,34 @@
require 'spec_helper'
RSpec.describe VersionCheckHelper do
describe '#version_status_badge' do
it 'returns nil if not dev environment and not enabled' do
stub_rails_env('development')
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { false }
let_it_be(:user) { create(:user) }
expect(helper.version_status_badge).to be(nil)
end
context 'when production and enabled' do
before do
stub_rails_env('production')
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { true }
allow(VersionCheck).to receive(:image_url) { 'https://version.host.com/check.svg?gitlab_info=xxx' }
describe '#show_version_check?' do
describe 'return conditions' do
where(:enabled, :consent, :is_admin, :result) do
[
[false, false, false, false],
[false, false, true, false],
[false, true, false, false],
[false, true, true, false],
[true, false, false, false],
[true, false, true, true],
[true, true, false, false],
[true, true, true, false]
]
end
it 'returns an image tag' do
expect(helper.version_status_badge).to start_with('<img')
end
it 'has a js prefixed css class' do
expect(helper.version_status_badge)
.to match(/class="js-version-status-badge lazy"/)
end
with_them do
before do
stub_application_setting(version_check_enabled: enabled)
allow(User).to receive(:single_user).and_return(double(user, requires_usage_stats_consent?: consent))
allow(helper).to receive(:current_user).and_return(user)
allow(user).to receive(:can_read_all_resources?).and_return(is_admin)
end
it 'has a VersionCheck image_url as the src' do
expect(helper.version_status_badge)
.to include(%{src="https://version.host.com/check.svg?gitlab_info=xxx"})
it 'returns correct results' do
expect(helper.show_version_check?).to eq result
end
end
end
end
......
......@@ -3,12 +3,6 @@
require 'spec_helper'
RSpec.describe VersionCheck do
describe '.image_url' do
it 'returns the correct URL' do
expect(described_class.image_url).to match(%r{\A#{Regexp.escape(described_class.host)}/check\.svg\?gitlab_info=\w+})
end
end
describe '.url' do
it 'returns the correct URL' do
expect(described_class.url).to match(%r{\A#{Regexp.escape(described_class.host)}/check\.json\?gitlab_info=\w+})
......
......@@ -459,6 +459,11 @@ RSpec.configure do |config|
end
end
# Ensures that any Javascript script that tries to make the external VersionCheck API call skips it and returns a response
config.before(:each, :js) do
allow_any_instance_of(VersionCheck).to receive(:response).and_return({ "severity" => "success" })
end
config.after(:each, :silence_stdout) do
$stdout = STDOUT
end
......
......@@ -9,8 +9,13 @@
require 'securerandom'
require 'socket'
require 'logger'
require 'bundler'
module GitalySetup
extend self
REPOS_STORAGE = 'default'
LOGGER = begin
default_name = ENV['CI'] ? 'DEBUG' : 'WARN'
level_name = ENV['GITLAB_TESTING_LOG_LEVEL']&.upcase
......@@ -54,9 +59,12 @@ module GitalySetup
{
'HOME' => expand_path('tmp/tests'),
'GEM_PATH' => Gem.path.join(':'),
'BUNDLE_APP_CONFIG' => File.join(gemfile_dir, '.bundle'),
'BUNDLE_INSTALL_FLAGS' => nil,
'BUNDLE_IGNORE_CONFIG' => '1',
'BUNDLE_PATH' => bundle_path,
'BUNDLE_GEMFILE' => gemfile,
'BUNDLE_JOBS' => '4',
'BUNDLE_RETRY' => '3',
'RUBYOPT' => nil,
# Git hooks can't run during tests as the internal API is not running.
......@@ -65,17 +73,20 @@ module GitalySetup
}
end
# rubocop:disable GitlabSecurity/SystemCommandInjection
def set_bundler_config
system('bundle config set --local jobs 4', chdir: gemfile_dir)
system('bundle config set --local retry 3', chdir: gemfile_dir)
def bundle_path
# Allow the user to override BUNDLE_PATH if they need to
return ENV['GITALY_TEST_BUNDLE_PATH'] if ENV['GITALY_TEST_BUNDLE_PATH']
if ENV['CI']
bundle_path = expand_path('vendor/gitaly-ruby')
system('bundle', 'config', 'set', '--local', 'path', bundle_path, chdir: gemfile_dir)
expand_path('vendor/gitaly-ruby')
else
explicit_path = Bundler.configured_bundle_path.explicit_path
return unless explicit_path
expand_path(explicit_path)
end
end
# rubocop:enable GitlabSecurity/SystemCommandInjection
def config_path(service)
case service
......@@ -88,6 +99,10 @@ module GitalySetup
end
end
def repos_path(storage = REPOS_STORAGE)
Gitlab.config.repositories.storages[REPOS_STORAGE].legacy_disk_path
end
def service_binary(service)
case service
when :gitaly, :gitaly2
......@@ -196,4 +211,104 @@ module GitalySetup
raise "could not connect to #{socket}"
end
def gitaly_socket_path
Gitlab::GitalyClient.address(REPOS_STORAGE).delete_prefix('unix:')
end
def gitaly_dir
socket_path = gitaly_socket_path
socket_path = File.expand_path(gitaly_socket_path) if expand_path_for_socket?
File.dirname(socket_path)
end
# Linux fails with "bind: invalid argument" if a UNIX socket path exceeds 108 characters:
# https://github.com/golang/go/issues/6895. We use absolute paths in CI to ensure
# that changes in the current working directory don't affect GRPC reconnections.
def expand_path_for_socket?
!!ENV['CI']
end
def setup_gitaly
unless ENV['CI']
# In CI Gitaly is built in the setup-test-env job and saved in the
# artifacts. So when tests are started, there's no need to build Gitaly.
build_gitaly
end
Gitlab::SetupHelper::Gitaly.create_configuration(
gitaly_dir,
{ 'default' => repos_path },
force: true,
options: {
prometheus_listen_addr: 'localhost:9236'
}
)
Gitlab::SetupHelper::Gitaly.create_configuration(
gitaly_dir,
{ 'default' => repos_path },
force: true,
options: {
internal_socket_dir: File.join(gitaly_dir, "internal_gitaly2"),
gitaly_socket: "gitaly2.socket",
config_filename: "gitaly2.config.toml"
}
)
Gitlab::SetupHelper::Praefect.create_configuration(gitaly_dir, { 'praefect' => repos_path }, force: true)
end
def socket_path(service)
File.join(tmp_tests_gitaly_dir, "#{service}.socket")
end
def praefect_socket_path
"unix:" + socket_path(:praefect)
end
def wait(service)
sleep_time = 10
sleep_interval = 0.1
socket = socket_path(service)
Integer(sleep_time / sleep_interval).times do
Socket.unix(socket)
return
rescue StandardError
sleep sleep_interval
end
raise "could not connect to #{service} at #{socket.inspect} after #{sleep_time} seconds"
end
def stop(pid)
Process.kill('KILL', pid)
rescue Errno::ESRCH
# The process can already be gone if the test run was INTerrupted.
end
def spawn_gitaly
check_gitaly_config!
gitaly_pid = start_gitaly
gitaly2_pid = start_gitaly2
praefect_pid = start_praefect
Kernel.at_exit do
# In CI this function is called by scripts/gitaly-test-spawn, triggered a
# before_script. Gitaly needs to remain running until the container is
# stopped.
next if ENV['CI']
pids = [gitaly_pid, gitaly2_pid, praefect_pid]
pids.each { |pid| stop(pid) }
end
wait('gitaly')
wait('praefect')
rescue StandardError
message = 'gitaly spawn failed'
message += " (try `rm -rf #{gitaly_dir}` ?)" unless ENV['CI']
raise message
end
end
# frozen_string_literal: true
require 'parallel'
require_relative 'gitaly_setup'
module TestEnv
extend self
......@@ -93,7 +94,6 @@ module TestEnv
}.freeze
TMP_TEST_PATH = Rails.root.join('tmp', 'tests').freeze
REPOS_STORAGE = 'default'
SECOND_STORAGE_PATH = Rails.root.join('tmp', 'tests', 'second_storage')
SETUP_METHODS = %i[setup_gitaly setup_gitlab_shell setup_workhorse setup_factory_repo setup_forked_repo].freeze
......@@ -128,7 +128,7 @@ module TestEnv
# Can be overriden
def post_init
start_gitaly(gitaly_dir)
start_gitaly
end
# Clean /tmp/tests
......@@ -142,7 +142,7 @@ module TestEnv
end
FileUtils.mkdir_p(
Gitlab::GitalyClient::StorageSettings.allow_disk_access { TestEnv.repos_path }
Gitlab::GitalyClient::StorageSettings.allow_disk_access { GitalySetup.repos_path }
)
FileUtils.mkdir_p(SECOND_STORAGE_PATH)
FileUtils.mkdir_p(backup_path)
......@@ -158,111 +158,28 @@ module TestEnv
def setup_gitaly
component_timed_setup('Gitaly',
install_dir: gitaly_dir,
install_dir: GitalySetup.gitaly_dir,
version: Gitlab::GitalyClient.expected_server_version,
task: "gitlab:gitaly:test_install",
task_args: [gitaly_dir, repos_path, gitaly_url].compact) do
Gitlab::SetupHelper::Gitaly.create_configuration(
gitaly_dir,
{ 'default' => repos_path },
force: true,
options: {
prometheus_listen_addr: 'localhost:9236'
}
)
Gitlab::SetupHelper::Gitaly.create_configuration(
gitaly_dir,
{ 'default' => repos_path },
force: true,
options: {
internal_socket_dir: File.join(gitaly_dir, "internal_gitaly2"),
gitaly_socket: "gitaly2.socket",
config_filename: "gitaly2.config.toml"
}
)
Gitlab::SetupHelper::Praefect.create_configuration(gitaly_dir, { 'praefect' => repos_path }, force: true)
end
end
def gitaly_socket_path
Gitlab::GitalyClient.address('default').sub(/\Aunix:/, '')
end
def gitaly_dir
socket_path = gitaly_socket_path
socket_path = File.expand_path(gitaly_socket_path) if expand_path?
File.dirname(socket_path)
end
# Linux fails with "bind: invalid argument" if a UNIX socket path exceeds 108 characters:
# https://github.com/golang/go/issues/6895. We use absolute paths in CI to ensure
# that changes in the current working directory don't affect GRPC reconnections.
def expand_path?
!!ENV['CI']
task: "gitlab:gitaly:clone",
fresh_install: ENV.key?('FORCE_GITALY_INSTALL'),
task_args: [GitalySetup.gitaly_dir, GitalySetup.repos_path, gitaly_url].compact) do
GitalySetup.setup_gitaly
end
end
def start_gitaly(gitaly_dir)
def start_gitaly
if ci?
# Gitaly has been spawned outside this process already
return
end
spawn_script = Rails.root.join('scripts/gitaly-test-spawn').to_s
Bundler.with_original_env do
unless system(spawn_script)
message = 'gitaly spawn failed'
message += " (try `rm -rf #{gitaly_dir}` ?)" unless ci?
raise message
end
end
gitaly_pid = Integer(File.read(TMP_TEST_PATH.join('gitaly.pid')))
gitaly2_pid = Integer(File.read(TMP_TEST_PATH.join('gitaly2.pid')))
praefect_pid = Integer(File.read(TMP_TEST_PATH.join('praefect.pid')))
Kernel.at_exit do
pids = [gitaly_pid, gitaly2_pid, praefect_pid]
pids.each { |pid| stop(pid) }
end
wait('gitaly')
wait('praefect')
end
def stop(pid)
Process.kill('KILL', pid)
rescue Errno::ESRCH
# The process can already be gone if the test run was INTerrupted.
GitalySetup.spawn_gitaly
end
def gitaly_url
ENV.fetch('GITALY_REPO_URL', nil)
end
def socket_path(service)
TMP_TEST_PATH.join('gitaly', "#{service}.socket").to_s
end
def praefect_socket_path
"unix:" + socket_path(:praefect)
end
def wait(service)
sleep_time = 10
sleep_interval = 0.1
socket = socket_path(service)
Integer(sleep_time / sleep_interval).times do
Socket.unix(socket)
return
rescue StandardError
sleep sleep_interval
end
raise "could not connect to #{service} at #{socket.inspect} after #{sleep_time} seconds"
end
# Feature specs are run through Workhorse
def setup_workhorse
# Always rebuild the config file
......@@ -378,8 +295,7 @@ module TestEnv
def rm_storage_dir(storage, dir)
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
repos_path = Gitlab.config.repositories.storages[storage].legacy_disk_path
target_repo_refs_path = File.join(repos_path, dir)
target_repo_refs_path = File.join(GitalySetup.repos_path(storage), dir)
FileUtils.remove_dir(target_repo_refs_path)
end
rescue Errno::ENOENT
......@@ -387,8 +303,7 @@ module TestEnv
def storage_dir_exists?(storage, dir)
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
repos_path = Gitlab.config.repositories.storages[storage].legacy_disk_path
File.exist?(File.join(repos_path, dir))
File.exist?(File.join(GitalySetup.repos_path(storage), dir))
end
end
......@@ -401,7 +316,7 @@ module TestEnv
end
def repos_path
@repos_path ||= Gitlab.config.repositories.storages[REPOS_STORAGE].legacy_disk_path
@repos_path ||= GitalySetup.repos_path
end
def backup_path
......@@ -522,7 +437,7 @@ module TestEnv
end
end
def component_timed_setup(component, install_dir:, version:, task:, task_args: [])
def component_timed_setup(component, install_dir:, version:, task:, fresh_install: true, task_args: [])
start = Time.now
ensure_component_dir_name_is_correct!(component, install_dir)
......@@ -532,7 +447,7 @@ module TestEnv
if component_needs_update?(install_dir, version)
# Cleanup the component entirely to ensure we start fresh
FileUtils.rm_rf(install_dir)
FileUtils.rm_rf(install_dir) if fresh_install
if ENV['SKIP_RAILS_ENV_IN_RAKE']
# When we run `scripts/setup-test-env`, we take care of loading the necessary dependencies
......
# frozen_string_literal: true
require_relative 'helpers/test_env'
require_relative 'helpers/gitaly_setup'
RSpec.configure do |config|
config.before(:each, :praefect) do
allow(Gitlab.config.repositories.storages['default']).to receive(:[]).and_call_original
allow(Gitlab.config.repositories.storages['default']).to receive(:[]).with('gitaly_address')
.and_return(TestEnv.praefect_socket_path)
.and_return(GitalySetup.praefect_socket_path)
end
end
......@@ -53,11 +53,14 @@ RSpec.describe 'admin/dashboard/index.html.haml' do
expect(rendered).not_to have_content "Users over License"
end
it 'links to the GitLab Changelog' do
stub_application_setting(version_check_enabled: true)
render
describe 'when show_version_check? is true' do
before do
allow(view).to receive(:show_version_check?).and_return(true)
render
end
expect(rendered).to have_link(href: 'https://gitlab.com/gitlab-org/gitlab/-/blob/master/CHANGELOG.md')
it 'renders the version check badge' do
expect(rendered).to have_selector('.js-gitlab-version-check')
end
end
end
......@@ -76,7 +76,6 @@ RSpec.describe 'help/index' do
def stub_helpers
allow(view).to receive(:markdown).and_return('')
allow(view).to receive(:version_status_badge).and_return('')
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
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