Skip to content

Commit a92fc66

Browse files
committed
test: Fix flaky full-stack tests by mocking service ports
Motivation: Multiple full-stack tests were failing intermittently when run in parallel due to "Address already in use" errors. They all defaulted to the same base port (9526), causing conflicts in concurrent test environments. Design Choices: - Restored mock_service_ports in OpenQA::Test::Utils which was previously removed as dead code in 8d7b9a5. It mocks OpenQA::Utils::service_port via Test::MockModule to allocate independent free ports per service using Mojo::IOLoop::Server->generate_port, which asks the OS for a truly free port instead of blindly guessing. - Applied mock_service_ports to t/05-scheduler-full.t, t/25-cache-service.t, t/33-developer_mode.t, t/43-scheduling-and-worker-scalability.t, and t/full-stack.t, replacing the prior setup_random_base_port approach. - Each service gets an independently chosen free port (not a fixed-offset block), eliminating the risk that one occupied port breaks all services. - Removed duplicate randomization logic and redundant comments across multiple test files. - Cleaned up BEGIN blocks and imports in full-stack tests. Benefits: - Guarantees free ports via OS-level allocation, eliminating race conditions that setup_random_base_port could not prevent. - Eliminates port conflicts during parallel test execution. - Improves test stability and reliability for CI/CD. - Centralizes logic for easier future maintenance and consistent port selection strategy. - Cleaner and more maintainable test code.
1 parent 91af0a8 commit a92fc66

6 files changed

Lines changed: 36 additions & 12 deletions

File tree

t/05-scheduler-full.t

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,17 @@ use OpenQA::Scheduler::Client;
2727
use OpenQA::Scheduler::Model::Jobs;
2828
use OpenQA::WebAPI;
2929
use OpenQA::Worker::WebUIConnection;
30-
use OpenQA::Utils;
30+
use OpenQA::Utils qw(service_port);
3131
require OpenQA::Test::Database;
3232
use OpenQA::Test::Utils qw(
33-
setup_mojo_app_with_default_worker_timeout
33+
mock_service_ports setup_mojo_app_with_default_worker_timeout
3434
setup_fullstack_temp_dir create_user_for_workers
3535
create_webapi setup_share_dir create_websocket_server
3636
stop_service unstable_worker
3737
unresponsive_worker broken_worker rejective_worker
3838
wait_for simulate_load
3939
);
40+
mock_service_ports;
4041
use OpenQA::Test::TimeLimit '150';
4142

4243
# treat this test like the fullstack test

t/25-cache-service.t

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@ $ENV{MOJO_LOG_LEVEL} = 'info';
1010
my $tempdir;
1111

1212
BEGIN {
13-
use Mojo::File qw(path tempdir);
14-
1513
$ENV{OPENQA_CACHE_SERVICE_QUIET} = $ENV{HARNESS_IS_VERBOSE} ? 0 : 1;
1614
$ENV{OPENQA_CACHE_ATTEMPTS} = 3;
1715
$ENV{OPENQA_CACHE_ATTEMPT_SLEEP_TIME} = 0;
1816
$ENV{OPENQA_RSYNC_RETRY_PERIOD} = 0;
1917
$ENV{OPENQA_RSYNC_RETRIES} = 1;
2018
$ENV{OPENQA_METRICS_DOWNLOAD_SIZE} = 1024;
21-
$ENV{OPENQA_BASE_PORT} = 20000 + int rand 10000;
19+
}
20+
21+
BEGIN {
22+
use Mojo::File qw(path tempdir);
2223
$ENV{OPENQA_TEST_WAIT_INTERVAL} = 0.05;
2324

2425
$tempdir = tempdir;
@@ -33,9 +34,6 @@ CACHEWORKERS = 10
3334
CACHELIMIT = 100');
3435
}
3536

36-
use FindBin;
37-
use lib "$FindBin::Bin/lib", "$FindBin::Bin/../external/os-autoinst-common/lib";
38-
3937
use Test::Warnings ':report_warnings';
4038
use Test::Output qw(stderr_like);
4139
use Test::Mojo;
@@ -46,15 +44,19 @@ use Mojo::IOLoop::Server;
4644
use POSIX '_exit';
4745
use Mojo::IOLoop::ReadWriteProcess qw(queue process);
4846
use Mojo::IOLoop::ReadWriteProcess::Session 'session';
47+
use FindBin;
48+
use lib "$FindBin::Bin/lib", "$FindBin::Bin/../external/os-autoinst-common/lib";
4949
use OpenQA::Test::Utils
50-
qw(fake_asset_server cache_minion_worker cache_worker_service wait_for_or_bail_out perform_minion_jobs wait_for);
50+
qw(mock_service_ports fake_asset_server cache_minion_worker cache_worker_service wait_for_or_bail_out perform_minion_jobs wait_for);
5151
use OpenQA::Test::TimeLimit '90';
5252
use Mojo::Util qw(md5_sum);
5353
use OpenQA::CacheService;
5454
use OpenQA::CacheService::Request;
5555
use OpenQA::CacheService::Client;
5656
use Time::Seconds;
5757

58+
mock_service_ports;
59+
5860
my $cachedir = $ENV{OPENQA_CACHE_DIR};
5961
my $port = Mojo::IOLoop::Server->generate_port;
6062
my $host = "http://localhost:$port";

t/33-developer_mode.t

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ use File::Path qw(make_path remove_tree);
3333
use Module::Load::Conditional 'can_load';
3434
use OpenQA::Utils qw(service_port);
3535
use OpenQA::Test::Utils qw(
36+
mock_service_ports
3637
create_websocket_server create_scheduler create_live_view_handler setup_share_dir setup_fullstack_temp_dir
3738
start_worker stop_service
3839
);
40+
mock_service_ports;
3941
use OpenQA::Test::FullstackUtils;
4042
use OpenQA::SeleniumTest;
4143

@@ -48,9 +50,13 @@ my $livehandler;
4850
my $scheduler;
4951

5052
sub turn_down_stack {
51-
stop_service($_) for ($worker, $ws, $livehandler, $scheduler);
53+
stop_service $scheduler;
54+
stop_service $livehandler;
55+
stop_service $ws;
56+
stop_service $worker;
5257
}
5358

59+
5460
driver_missing unless check_driver_modules;
5561

5662
# setup directories

t/43-scheduling-and-worker-scalability.t

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ require OpenQA::Test::Database;
2323
use OpenQA::Jobs::Constants;
2424
use OpenQA::Log qw(setup_log);
2525
use OpenQA::Test::Utils qw(
26-
setup_mojo_app_with_default_worker_timeout
26+
mock_service_ports setup_mojo_app_with_default_worker_timeout
2727
create_user_for_workers create_webapi create_websocket_server
2828
stop_service setup_fullstack_temp_dir simulate_load);
29+
mock_service_ports;
2930
use OpenQA::Test::TimeLimit '20';
3031
use OpenQA::Utils 'testcasedir';
3132

t/full-stack.t

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ use Module::Load::Conditional 'can_load';
4949
use OpenQA::Test::Utils
5050
qw(create_websocket_server create_live_view_handler setup_share_dir),
5151
qw(cache_minion_worker cache_worker_service setup_fullstack_temp_dir),
52-
qw(start_worker stop_service wait_for_or_bail_out);
52+
qw(mock_service_ports start_worker stop_service wait_for_or_bail_out);
53+
mock_service_ports;
5354
use OpenQA::Test::FullstackUtils;
5455

5556
plan skip_all => 'set FULLSTACK=1 (be careful)' unless $ENV{FULLSTACK};

t/lib/OpenQA/Test/Utils.pm

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ BEGIN {
5050

5151
our (@EXPORT, @EXPORT_OK);
5252
@EXPORT_OK = qw(
53+
mock_service_ports
5354
setup_mojo_app_with_default_worker_timeout create_user_for_workers
5455
create_webapi create_websocket_server create_scheduler create_live_view_handler
5556
unresponsive_worker broken_worker rejective_worker setup_share_dir setup_fullstack_temp_dir run_gru_job
@@ -71,6 +72,18 @@ our (@EXPORT, @EXPORT_OK);
7172
#
7273
# Potentially this approach can also be used in production code.
7374

75+
sub mock_service_ports {
76+
my %ports;
77+
Test::MockModule->new('OpenQA::Utils')->redefine(
78+
service_port => sub {
79+
my ($service) = @_;
80+
my $port = $ports{$service} //= Mojo::IOLoop::Server->generate_port;
81+
note("Mocking service port for $service to be $port");
82+
return $port;
83+
});
84+
note('Used ports: ' . dumper(\%ports));
85+
}
86+
7487
sub setup_mojo_app_with_default_worker_timeout ($class = 'Mojolicious') {
7588
my $app = $class->new(config => {global => {worker_timeout => DEFAULT_WORKER_TIMEOUT}}, log => undef);
7689
OpenQA::App->set_singleton($app);

0 commit comments

Comments
 (0)