From e45bc8c6e02711cba8d5cda37838629f7b8e51ef Mon Sep 17 00:00:00 2001 From: Maxime Noel Date: Tue, 4 Mar 2025 16:12:10 +1300 Subject: [PATCH] Fail fast when we have read timeout --- .../reposync/srv_sync_channels.feature | 4 +- .../features/step_definitions/common_steps.rb | 2 +- .../step_definitions/navigation_steps.rb | 7 +- testsuite/features/support/env.rb | 85 +++++++++++++++---- 4 files changed, 72 insertions(+), 26 deletions(-) diff --git a/testsuite/features/reposync/srv_sync_channels.feature b/testsuite/features/reposync/srv_sync_channels.feature index c3813481ce58..b66b57719b23 100644 --- a/testsuite/features/reposync/srv_sync_channels.feature +++ b/testsuite/features/reposync/srv_sync_channels.feature @@ -31,8 +31,8 @@ Feature: List available channels and enable them When I execute mgr-sync "list products" Then I should get "[ ] SUSE Manager Proxy 4.3 x86_64" -@proxy -@susemanager + # The SCC mirror in github validation does not contain SUSE Manager products +@skip_if_github_validation Scenario: List all products for SUSE Manager When I execute mgr-sync "list products --expand" Then I should get "[ ] SUSE Linux Enterprise Server 15 SP4 x86_64" diff --git a/testsuite/features/step_definitions/common_steps.rb b/testsuite/features/step_definitions/common_steps.rb index 9903c320ef3c..deeca2261881 100644 --- a/testsuite/features/step_definitions/common_steps.rb +++ b/testsuite/features/step_definitions/common_steps.rb @@ -180,7 +180,7 @@ And I follow first "#{event}" And I wait until I see "This action will be executed after" text And I wait until I see "#{event}" text - And I wait at most #{complete_timeout} seconds until the "#{event}" is completed, refreshing the page + And I wait at most #{complete_timeout} seconds until the event is completed, refreshing the page ) end diff --git a/testsuite/features/step_definitions/navigation_steps.rb b/testsuite/features/step_definitions/navigation_steps.rb index 3f5d6eed2389..26ebdaaebf8d 100644 --- a/testsuite/features/step_definitions/navigation_steps.rb +++ b/testsuite/features/step_definitions/navigation_steps.rb @@ -81,17 +81,12 @@ end end -When(/^I wait at most (\d+) seconds until the "([^"]*)" is completed, refreshing the page$/) do |timeout, event| +When(/^I wait at most (\d+) seconds until the event is completed, refreshing the page$/) do |timeout| last = Time.now next if has_content?('This action\'s status is: Completed.', wait: 3) repeat_until_timeout(timeout: timeout.to_i, message: 'Event not yet completed') do break if has_content?('This action\'s status is: Completed.', wait: 3) - - if has_content?('Minion is down or could not be contacted.', wait: 3) - find(:xpath, "//input[@value='Reschedule']").click - step %(I wait 30 seconds until the event is picked up and #{timeout} seconds until the event "#{event}" is completed) - end raise SystemCallError, 'Event failed' if has_content?('This action\'s status is: Failed.', wait: 3) current = Time.now diff --git a/testsuite/features/support/env.rb b/testsuite/features/support/env.rb index 738415b736a3..234f448f741c 100644 --- a/testsuite/features/support/env.rb +++ b/testsuite/features/support/env.rb @@ -20,6 +20,7 @@ require_relative 'quality_intelligence' require_relative 'remote_nodes_env' require_relative 'commonlib' +require 'net/http' $stdout.puts("Using Ruby version: #{RUBY_VERSION}") @@ -73,6 +74,10 @@ $beta_enabled = (ENV.fetch('BETA_ENABLED', 'False') == 'True') $api_protocol = ENV.fetch('API_PROTOCOL', nil) if ENV['API_PROTOCOL'] # force the API protocol to be used. You can use 'http' or 'xmlrpc' +# Define a global counter to track consecutive ReadTimeout errors +$timeout_failure_count = 0 +$timeout_threshold = 10 # Set the threshold for stopping the suite + # QAM and Build Validation pipelines will provide a json file including all custom (MI) repositories custom_repos_path = "#{File.dirname(__FILE__)}/../upload_files/custom_repositories.json" if File.exist?(custom_repos_path) @@ -130,26 +135,80 @@ def capybara_register_driver # Define the current feature scope Before do |scenario| $feature_scope = scenario.location.file.split(%r{(\.feature|/)})[-2] + if $timeout_failure_count >= $timeout_threshold + warn "Timeout failure threshold reached, stopping test suite." + exit(1) + end + feature_path = scenario.location.file + $feature_filename = feature_path.split(%r{(\.feature|/)})[-2] + next if get_context('user_created') == true + + # Create own user based on feature filename. Exclude core, reposync, finishing and build_validation features. + unless feature_path.match?(/core|reposync|finishing|build_validation/) + step %(I create a user with name "#{$feature_filename}" and password "linux") + add_context('user_created', true) + end end -# Embed a screenshot after each failed scenario +# After hook to handle scenario failures and dump feature code coverage into Redis DB After do |scenario| + # Handle timeout failure threshold + if $timeout_failure_count >= $timeout_threshold + warn "Timeout failure threshold reached, stopping test suite." + exit(1) + end + current_epoch = Time.new.to_i log "This scenario took: #{current_epoch - @scenario_start_time} seconds" + + # Check if the scenario failed due to a Net::ReadTimeout if scenario.failed? - begin - if web_session_is_active? - handle_screenshot_and_relog(scenario, current_epoch) - else - warn 'There is no active web session; unable to take a screenshot or relog.' + if scenario.exception.is_a?(Net::ReadTimeout) + $timeout_failure_count += 1 + warn "Net::ReadTimeout detected! Consecutive ReadTimeouts: #{$timeout_failure_count}" + log_server_response_time + else + begin + if web_session_is_active? + handle_screenshot_and_relog(scenario, current_epoch) + else + warn 'There is no active web session; unable to take a screenshot or relog.' + end + ensure + print_server_logs end - ensure - print_server_logs end end + + # Check if we need to dump code coverage into Redis DB + next unless $code_coverage_mode + next unless $feature_path != scenario.location.file + + process_code_coverage + $feature_path = scenario.location.file + + # Check if the consecutive timeout threshold is reached + if $timeout_failure_count >= $timeout_threshold + warn "Reached the timeout failure threshold of #{$timeout_threshold} consecutive ReadTimeouts. Stopping the test suite." + exit(1) + end + page.instance_variable_set(:@touched, false) if Capybara::Session.instance_created? end +# Method to check server response time (could be using an HTTP request or ping) +def log_server_response_time + begin + start_time = Time.now + uri = URI.parse("https://#{ENV.fetch('SERVER', nil)}") + Net::HTTP.get_response(uri) + response_time = Time.now - start_time + log "Server response time: #{response_time} seconds" + rescue => e + warn "Error checking server response time: #{e.message}" + end +end + # Test is web session is open def web_session_is_active? return false unless Capybara::Session.instance_created? @@ -197,6 +256,7 @@ def relog_and_visit_previous_url end rescue Timeout::Error warn "Timed out while attempting to relog and visit the previous URL: #{current_url}" + $timeout_failure_count += 1 rescue StandardError => e warn "An error occurred while relogging and visiting the previous URL: #{e.message}" end @@ -211,15 +271,6 @@ def process_code_coverage $code_coverage.push_feature_coverage(feature_filename) end -# Dump feature code coverage into a Redis DB -After do |scenario| - next unless $code_coverage_mode - next unless $feature_path != scenario.location.file - - process_code_coverage - $feature_path = scenario.location.file -end - # Dump feature code coverage into a Redis DB, for the last feature AfterAll do next unless $code_coverage_mode