From 765ca40d65c6644d2158eb04898a0f3d6e316e31 Mon Sep 17 00:00:00 2001 From: Mark Lapierre Date: Tue, 23 Oct 2018 14:54:00 -0400 Subject: [PATCH] Add e2e test of push over SSH over Git protocol v2 Adds a new end-to-end test to check that Git protocol v2 can be used to push over SSH. Includes a change in Git::Repository to use Runtime::Env.debug? to enable logging instead of .verbose? --- qa/qa/git/repository.rb | 26 +++++- qa/qa/runtime/env.rb | 19 +++++ .../repository/protocol_v2_push_ssh_spec.rb | 84 +++++++++++++++++++ qa/qa/specs/runner.rb | 4 + qa/spec/git/repository_spec.rb | 30 +++++++ qa/spec/runtime/env_spec.rb | 47 ++++++++--- qa/spec/specs/runner_spec.rb | 14 ++++ 7 files changed, 211 insertions(+), 13 deletions(-) create mode 100644 qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb index 27a88534258..7f959441dac 100644 --- a/qa/qa/git/repository.rb +++ b/qa/qa/git/repository.rb @@ -109,6 +109,28 @@ module QA known_hosts_file.close(true) end + def push_with_git_protocol(version, file_name, file_content, commit_message = 'Initial commit') + self.git_protocol = version + add_file(file_name, file_content) + commit(commit_message) + push_changes + + fetch_supported_git_protocol + end + + def git_protocol=(value) + raise ArgumentError, "Please specify the protocol you would like to use: 0, 1, or 2" unless %w[0 1 2].include?(value.to_s) + + run("git config protocol.version #{value}") + end + + def fetch_supported_git_protocol + # ls-remote is one command known to respond to Git protocol v2 so we use + # it to get output including the version reported via Git tracing + output = run("git ls-remote #{uri}", "GIT_TRACE_PACKET=1") + output[/git< version (\d+)/, 1] || 'unknown' + end + private attr_reader :uri, :username, :password, :known_hosts_file, :private_key_file @@ -117,8 +139,8 @@ module QA !private_key_file.nil? end - def run(command_str) - command = [env_vars, command_str, '2>&1'].compact.join(' ') + def run(command_str, *extra_env) + command = [env_vars, *extra_env, command_str, '2>&1'].compact.join(' ') Runtime::Logger.debug "Git: command=[#{command}]" output, _ = Open3.capture2(command) diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index c7052a9f300..c4500f9be90 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -7,6 +7,16 @@ module QA attr_writer :personal_access_token + # The environment variables used to indicate if the environment under test + # supports the given feature + SUPPORTED_FEATURES = { + git_protocol_v2: 'QA_CAN_TEST_GIT_PROTOCOL_V2' + }.freeze + + def supported_features + SUPPORTED_FEATURES + end + def debug? enabled?(ENV['QA_DEBUG'], default: false) end @@ -104,6 +114,15 @@ module QA raise ArgumentError, "Please provide GITHUB_ACCESS_TOKEN" end + # Returns true if there is an environment variable that indicates that + # the feature is supported in the environment under test. + # All features are supported by default. + def can_test?(feature) + raise ArgumentError, %Q(Unknown feature "#{feature}") unless SUPPORTED_FEATURES.include? feature + + enabled?(ENV[SUPPORTED_FEATURES[feature]], default: true) + end + private def enabled?(value, default: true) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb new file mode 100644 index 00000000000..bc88e6450f5 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module QA + context 'Create' do + describe 'Push over SSH using Git protocol version 2', :requires_git_protocol_v2 do + # Note: If you run this test against GDK make sure you've enabled sshd and + # enabled setting the Git protocol by adding `AcceptEnv GIT_PROTOCOL` to + # `sshd_config` + # See: https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/run_qa_against_gdk.md + + let(:key_title) { "key for ssh tests #{Time.now.to_f}" } + let(:ssh_key) do + Factory::Resource::SSHKey.fabricate! do |resource| + resource.title = key_title + end + end + + def login + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + end + + around do |example| + # Create an SSH key to be used with Git + login + ssh_key + + example.run + + # Remove the SSH key + login + Page::Main::Menu.perform(&:go_to_profile_settings) + Page::Profile::Menu.perform(&:click_ssh_keys) + Page::Profile::SSHKeys.perform do |ssh_keys| + ssh_keys.remove_key(key_title) + end + end + + it 'user pushes to the repository' do + # Create a project to push to + project = Factory::Resource::Project.fabricate! do |project| + project.name = 'git-protocol-project' + end + + file_name = 'README.md' + file_content = 'Test Git protocol v2' + git_protocol = '2' + git_protocol_reported = nil + + # Use Git to clone the project, push a file to it, and then check the + # supported Git protocol + Git::Repository.perform do |repository| + username = 'GitLab QA' + email = 'root@gitlab.com' + + repository.uri = project.repository_ssh_location.uri + + begin + repository.use_ssh_key(ssh_key) + repository.clone + repository.configure_identity(username, email) + + git_protocol_reported = repository.push_with_git_protocol( + git_protocol, + file_name, + file_content) + ensure + repository.delete_ssh_key + end + end + + project.visit! + Page::Project::Show.perform(&:wait_for_push) + + # Check that the push worked + expect(page).to have_content(file_name) + expect(page).to have_content(file_content) + + # And check that the correct Git protocol was used + expect(git_protocol_reported).to eq(git_protocol) + end + end + end +end diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb index ad397c13f0c..1bd8101c36d 100644 --- a/qa/qa/specs/runner.rb +++ b/qa/qa/specs/runner.rb @@ -25,6 +25,10 @@ module QA args.push(%w[--tag ~skip_signup_disabled]) if QA::Runtime::Env.signup_disabled? + QA::Runtime::Env.supported_features.each_key do |key| + args.push(["--tag", "~requires_#{key}"]) unless QA::Runtime::Env.can_test? key + end + args.push(options) args.push(DEFAULT_TEST_PATH_ARGS) unless options.any? { |opt| opt =~ %r{/features/} } diff --git a/qa/spec/git/repository_spec.rb b/qa/spec/git/repository_spec.rb index c629f802aa4..faa154c78da 100644 --- a/qa/spec/git/repository_spec.rb +++ b/qa/spec/git/repository_spec.rb @@ -26,6 +26,36 @@ describe QA::Git::Repository do end end + describe '#git_protocol=' do + [0, 1, 2].each do |version| + it "configures git to use protocol version #{version}" do + expect(repository).to receive(:run).with("git config protocol.version #{version}") + repository.git_protocol = version + end + end + + it 'raises an error if the version is unsupported' do + expect { repository.git_protocol = 'foo' }.to raise_error(ArgumentError, "Please specify the protocol you would like to use: 0, 1, or 2") + end + end + + describe '#fetch_supported_git_protocol' do + it "reports the detected version" do + expect(repository).to receive(:run).and_return("packet: git< version 2") + expect(repository.fetch_supported_git_protocol).to eq('2') + end + + it 'reports unknown if version is unknown' do + expect(repository).to receive(:run).and_return("packet: git< version -1") + expect(repository.fetch_supported_git_protocol).to eq('unknown') + end + + it 'reports unknown if content does not identify a version' do + expect(repository).to receive(:run).and_return("foo") + expect(repository.fetch_supported_git_protocol).to eq('unknown') + end + end + def cd_empty_temp_directory tmp_dir = 'tmp/git-repository-spec/' FileUtils.rm_rf(tmp_dir) if ::File.exist?(tmp_dir) diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb index c59c415c148..ded51d5bb7c 100644 --- a/qa/spec/runtime/env_spec.rb +++ b/qa/spec/runtime/env_spec.rb @@ -3,49 +3,62 @@ describe QA::Runtime::Env do include Support::StubENV - shared_examples 'boolean method' do |method, env_key, default| + shared_examples 'boolean method' do |**kwargs| + it_behaves_like 'boolean method with parameter', kwargs + end + + shared_examples 'boolean method with parameter' do |method:, param: nil, env_key:, default:| context 'when there is an env variable set' do it 'returns false when falsey values specified' do stub_env(env_key, 'false') - expect(described_class.public_send(method)).to be_falsey + expect(described_class.public_send(method, *param)).to be_falsey stub_env(env_key, 'no') - expect(described_class.public_send(method)).to be_falsey + expect(described_class.public_send(method, *param)).to be_falsey stub_env(env_key, '0') - expect(described_class.public_send(method)).to be_falsey + expect(described_class.public_send(method, *param)).to be_falsey end it 'returns true when anything else specified' do stub_env(env_key, 'true') - expect(described_class.public_send(method)).to be_truthy + expect(described_class.public_send(method, *param)).to be_truthy stub_env(env_key, '1') - expect(described_class.public_send(method)).to be_truthy + expect(described_class.public_send(method, *param)).to be_truthy stub_env(env_key, 'anything') - expect(described_class.public_send(method)).to be_truthy + expect(described_class.public_send(method, *param)).to be_truthy end end context 'when there is no env variable set' do it "returns the default, #{default}" do stub_env(env_key, nil) - expect(described_class.public_send(method)).to be(default) + expect(described_class.public_send(method, *param)).to be(default) end end end describe '.signup_disabled?' do - it_behaves_like 'boolean method', :signup_disabled?, 'SIGNUP_DISABLED', false + it_behaves_like 'boolean method', + method: :signup_disabled?, + env_key: 'SIGNUP_DISABLED', + default: false end describe '.debug?' do - it_behaves_like 'boolean method', :debug?, 'QA_DEBUG', false + it_behaves_like 'boolean method', + method: :debug?, + env_key: 'QA_DEBUG', + default: false end describe '.chrome_headless?' do - it_behaves_like 'boolean method', :chrome_headless?, 'CHROME_HEADLESS', true + it_behaves_like 'boolean method', + method: :chrome_headless?, + env_key: 'CHROME_HEADLESS', + default: true end describe '.running_in_ci?' do @@ -182,4 +195,16 @@ describe QA::Runtime::Env do expect(described_class.log_destination).to eq('path/to_file') end end + + describe '.can_test?' do + it_behaves_like 'boolean method with parameter', + method: :can_test?, + param: :git_protocol_v2, + env_key: 'QA_CAN_TEST_GIT_PROTOCOL_V2', + default: true + + it 'raises ArgumentError if feature is unknown' do + expect { described_class.can_test? :foo }.to raise_error(ArgumentError, 'Unknown feature "foo"') + end + end end diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb index 9ddaf7ab1b3..741821ddf8c 100644 --- a/qa/spec/specs/runner_spec.rb +++ b/qa/spec/specs/runner_spec.rb @@ -76,6 +76,20 @@ describe QA::Specs::Runner do end end + context 'when git protocol v2 is not supported' do + before do + allow(QA::Runtime::Env).to receive(:can_test?).with(:git_protocol_v2).and_return(false) + end + + subject { described_class.new } + + it 'it includes default args and excludes the requires_git_protocol_v2 tag' do + expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~requires_git_protocol_v2', *described_class::DEFAULT_TEST_PATH_ARGS]) + + subject.perform + end + end + def expect_rspec_runner_arguments(arguments) expect(RSpec::Core::Runner).to receive(:run) .with(arguments, $stderr, $stdout)