Commit f1083234 authored by Andrejs Cunskis's avatar Andrejs Cunskis

Merge branch 'write-resource-to-env' into 'master'

Collect test resources and write to file

See merge request gitlab-org/gitlab!77431
parents b6792b4b 80bd7a36
......@@ -59,4 +59,9 @@ desc "Deletes projects directly under the provided group"
task :delete_projects do
QA::Tools::DeleteProjects.new.run
end
desc "Deletes resources created during E2E test runs"
task :delete_test_resources, :file_pattern do |t, args|
QA::Tools::DeleteTestResources.new(args[:file_pattern]).run
end
# rubocop:enable Rails/RakeEnvironment
......@@ -71,28 +71,33 @@ module QA
resource_web_url = yield
resource.web_url = resource_web_url
QA::Tools::TestResourceDataProcessor.collect(resource, resource_identifier(resource))
resource
end
def resource_identifier(resource)
if resource.respond_to?(:username) && resource.username
"with username '#{resource.username}'"
elsif resource.respond_to?(:full_path) && resource.full_path
"with full_path '#{resource.full_path}'"
elsif resource.respond_to?(:name) && resource.name
"with name '#{resource.name}'"
elsif resource.respond_to?(:id) && resource.id
"with id '#{resource.id}'"
elsif resource.respond_to?(:iid) && resource.iid
"with iid '#{resource.iid}'"
end
rescue QA::Resource::Base::NoValueError
nil
end
def log_fabrication(method, resource, parents, args)
start = Time.now
Support::FabricationTracker.start_fabrication
result = yield.tap do
fabrication_time = Time.now - start
resource_identifier = begin
if resource.respond_to?(:username) && resource.username
"with username '#{resource.username}'"
elsif resource.respond_to?(:full_path) && resource.full_path
"with full_path '#{resource.full_path}'"
elsif resource.respond_to?(:name) && resource.name
"with name '#{resource.name}'"
elsif resource.respond_to?(:id) && resource.id
"with id '#{resource.id}'"
end
rescue QA::Resource::Base::NoValueError
nil
end
fabrication_http_method = if resource.api_fabrication_http_method == :get
if self.include?(Reusable)
......@@ -108,7 +113,7 @@ module QA
Runtime::Logger.debug do
msg = ["==#{'=' * parents.size}>"]
msg << "#{fabrication_http_method} a #{name}"
msg << resource_identifier if resource_identifier
msg << resource_identifier(resource) if resource_identifier(resource)
msg << "as a dependency of #{parents.last}" if parents.any?
msg << "via #{method}"
msg << "in #{fabrication_time} seconds"
......
......@@ -429,6 +429,11 @@ module QA
running_in_ci? && enabled?(ENV['QA_EXPORT_TEST_METRICS'], default: true)
end
def test_resources_created_filepath
file_name = running_in_ci? ? "test-resources-#{SecureRandom.hex(3)}.json" : 'test-resources.json'
ENV.fetch('QA_TEST_RESOURCES_CREATED_FILEPATH', File.join(Path.qa_root, 'tmp', file_name))
end
private
def remote_grid_credentials
......
# frozen_string_literal: true
# This script reads from test_resources.txt file to collect data about resources to delete
# Deletes all deletable resources that E2E tests created
# Resource type: Sandbox, User, Fork and RSpec::Mocks::Double are not included
#
# Required environment variables: GITLAB_QA_ACCESS_TOKEN and GITLAB_ADDRESS
# When in CI also requires: QA_TEST_RESOURCES_FILE_PATTERN
# Run `rake delete_test_resources[<file_pattern>]`
module QA
module Tools
class DeleteTestResources
include Support::API
def initialize(file_pattern = nil)
raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS']
raise ArgumentError, "Please provide GITLAB_QA_ACCESS_TOKEN" unless ENV['GITLAB_QA_ACCESS_TOKEN']
@api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN'])
@file_pattern = file_pattern
end
def run
puts 'Deleting test created resources...'
if Runtime::Env.running_in_ci?
raise ArgumentError, 'Please provide QA_TEST_RESOURCES_FILE_PATTERN' unless ENV['QA_TEST_RESOURCES_FILE_PATTERN']
Dir.glob(@file_pattern).each do |file|
delete_resources(load_file(file))
end
else
file = Runtime::Env.test_resources_created_filepath
raise ArgumentError, "'#{file}' either does not exist or empty." if !File.exist?(file) || File.zero?(file)
delete_resources(load_file(file))
end
puts "\nDone"
end
private
def load_file(json)
JSON.parse(File.read(json))
end
def delete_resources(resources)
failures = []
resources.each_key do |type|
next if resources[type].empty?
resources[type].each do |resource|
next if resource_not_found?(resource['api_path'])
msg = resource['info'] ? "#{type} - #{resource['info']}" : "#{type} at #{resource['api_path']}"
puts "\nDeleting #{msg}..."
delete_response = delete(Runtime::API::Request.new(@api_client, resource['api_path']).url)
if delete_response.code == 202
print "\e[32m.\e[0m"
else
print "\e[31mF\e[0m"
failures << msg
end
end
end
unless failures.empty?
puts "\nFailed to delete #{failures.length} resources:\n"
puts failures
end
end
def resource_not_found?(api_path)
get_response = get Runtime::API::Request.new(@api_client, api_path).url
get_response.code.eql? 404
end
end
end
end
# frozen_string_literal: true
# This script collects all resources created during each test execution
# Save the data and write it to a JSON file at the end of suite
module QA
module Tools
class TestResourceDataProcessor
@resources ||= Hash.new { |hsh, key| hsh[key] = [] }
class << self
# Ignoring rspec-mocks, sandbox, user and fork resources
# TODO: Will need to figure out which user resources can be collected, ignore for now
#
# Collecting resources created in E2E tests
# Data is a Hash of resources with keys as resource type (group, project, issue, etc.)
# Each type contains an array of resource object (hash) of the same type
# E.g: { "QA::Resource::Project": [ { info: 'foo', api_path: '/foo'}, {...} ] }
def collect(resource, info)
return if resource.api_response.nil? ||
resource.is_a?(RSpec::Mocks::Double) ||
resource.is_a?(Resource::Sandbox) ||
resource.is_a?(Resource::User) ||
resource.is_a?(Resource::Fork)
api_path = if resource.respond_to?(:api_delete_path)
resource.api_delete_path.gsub('%2F', '/')
elsif resource.respond_to?(:api_get_path)
resource.api_get_path.gsub('%2F', '/')
else
'Cannot find resource API path'
end
type = resource.class.name
@resources[type] << { info: info, api_path: api_path }
end
# If JSON file exists and not empty, read and load file content
# Merge what is saved in @resources into the content from file
# Overwrite file content with the new data hash
# Otherwise create file and write data hash to file for the first time
def write_to_file
return if @resources.empty?
file = Runtime::Env.test_resources_created_filepath
FileUtils.mkdir_p('tmp/')
FileUtils.touch(file)
data = nil
if File.zero?(file)
data = @resources
else
data = JSON.parse(File.read(file))
@resources.each_pair do |key, val|
data[key].nil? ? data[key] = val : val.each { |item| data[key] << item }
end
end
File.open(file, 'w') { |f| f.write(JSON.pretty_generate(data.each_value(&:uniq!))) }
end
end
end
end
end
......@@ -360,4 +360,36 @@ RSpec.describe QA::Runtime::Env do
end
end
end
describe '.test_resources_created_filepath' do
context 'when not in CI' do
before do
allow(described_class).to receive(:running_in_ci?).and_return(false)
end
it 'returns default path if QA_TEST_RESOURCES_CREATED_FILEPATH is not defined' do
stub_env('QA_TEST_RESOURCES_CREATED_FILEPATH', nil)
expect(described_class.test_resources_created_filepath).to include('tmp/test-resources.json')
end
it 'returns path if QA_TEST_RESOURCES_CREATED_FILEPATH is defined' do
stub_env('QA_TEST_RESOURCES_CREATED_FILEPATH', 'path/to_file')
expect(described_class.test_resources_created_filepath).to eq('path/to_file')
end
end
context 'when in CI' do
before do
allow(described_class).to receive(:running_in_ci?).and_return(true)
allow(SecureRandom).to receive(:hex).with(3).and_return('abc123')
stub_env('QA_TEST_RESOURCES_CREATED_FILEPATH', nil)
end
it 'returns path with random hex in file name' do
expect(described_class.test_resources_created_filepath).to include('tmp/test-resources-abc123.json')
end
end
end
end
......@@ -67,6 +67,9 @@ RSpec.configure do |config|
config.after(:suite) do |suite|
# If any tests failed, leave the resources behind to help troubleshoot
QA::Resource::ReusableProject.remove_all_via_api! unless suite.reporter.failed_examples.present?
# Write all test created resources to JSON file
QA::Tools::TestResourceDataProcessor.write_to_file
end
config.append_after(:suite) do
......
# frozen_string_literal: true
RSpec.describe QA::Tools::TestResourceDataProcessor do
let(:info) { 'information' }
let(:api_path) { '/foo' }
let(:result) { [{ info: info, api_path: api_path }] }
describe '.collect' do
context 'when resource is not restricted' do
let(:resource) { instance_double(QA::Resource::Project, api_delete_path: '/foo', api_response: 'foo') }
it 'collects resource' do
expect(described_class.collect(resource, info)).to eq(result)
end
end
context 'when resource api response is nil' do
let(:resource) { double(QA::Resource::Project, api_delete_path: '/foo', api_response: nil) }
it 'does not collect resource' do
expect(described_class.collect(resource, info)).to eq(nil)
end
end
context 'when resource is restricted' do
let(:resource) { double(QA::Resource::Sandbox, api_delete_path: '/foo', api_response: 'foo') }
it 'does not collect resource' do
expect(described_class.collect(resource, info)).to eq(nil)
end
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