Commit 80bd7a36 authored by Tiffany Rea's avatar Tiffany Rea Committed by Andrejs Cunskis

Collect test resources and write to file

Add rake task for delete test resource task
Update Resouce::Base to collect resource after creation
Add new ENV var for JSON file path
Add new tool to delete test resources
Add new tool to collect test resources
Update spec helper to write resource collection to file at end of suite
parent 4a36c773
...@@ -59,4 +59,9 @@ desc "Deletes projects directly under the provided group" ...@@ -59,4 +59,9 @@ desc "Deletes projects directly under the provided group"
task :delete_projects do task :delete_projects do
QA::Tools::DeleteProjects.new.run QA::Tools::DeleteProjects.new.run
end 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 # rubocop:enable Rails/RakeEnvironment
...@@ -71,28 +71,33 @@ module QA ...@@ -71,28 +71,33 @@ module QA
resource_web_url = yield resource_web_url = yield
resource.web_url = resource_web_url resource.web_url = resource_web_url
QA::Tools::TestResourceDataProcessor.collect(resource, resource_identifier(resource))
resource resource
end 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) def log_fabrication(method, resource, parents, args)
start = Time.now start = Time.now
Support::FabricationTracker.start_fabrication Support::FabricationTracker.start_fabrication
result = yield.tap do result = yield.tap do
fabrication_time = Time.now - start 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 fabrication_http_method = if resource.api_fabrication_http_method == :get
if self.include?(Reusable) if self.include?(Reusable)
...@@ -108,7 +113,7 @@ module QA ...@@ -108,7 +113,7 @@ module QA
Runtime::Logger.debug do Runtime::Logger.debug do
msg = ["==#{'=' * parents.size}>"] msg = ["==#{'=' * parents.size}>"]
msg << "#{fabrication_http_method} a #{name}" 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 << "as a dependency of #{parents.last}" if parents.any?
msg << "via #{method}" msg << "via #{method}"
msg << "in #{fabrication_time} seconds" msg << "in #{fabrication_time} seconds"
......
...@@ -429,6 +429,11 @@ module QA ...@@ -429,6 +429,11 @@ module QA
running_in_ci? && enabled?(ENV['QA_EXPORT_TEST_METRICS'], default: true) running_in_ci? && enabled?(ENV['QA_EXPORT_TEST_METRICS'], default: true)
end 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 private
def remote_grid_credentials 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 ...@@ -360,4 +360,36 @@ RSpec.describe QA::Runtime::Env do
end end
end 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 end
...@@ -67,6 +67,9 @@ RSpec.configure do |config| ...@@ -67,6 +67,9 @@ RSpec.configure do |config|
config.after(:suite) do |suite| config.after(:suite) do |suite|
# If any tests failed, leave the resources behind to help troubleshoot # If any tests failed, leave the resources behind to help troubleshoot
QA::Resource::ReusableProject.remove_all_via_api! unless suite.reporter.failed_examples.present? 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 end
config.append_after(:suite) do 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