Skip to content

Fixed the issue #5550.Supports specifying port ranges, with automatic matching between listen and URI.#6280

Closed
xianglongfei-8888 wants to merge 1 commit intoavocado-framework:masterfrom
xianglongfei-8888:dev
Closed

Fixed the issue #5550.Supports specifying port ranges, with automatic matching between listen and URI.#6280
xianglongfei-8888 wants to merge 1 commit intoavocado-framework:masterfrom
xianglongfei-8888:dev

Conversation

@xianglongfei-8888
Copy link

@xianglongfei-8888 xianglongfei-8888 commented Mar 4, 2026

Fixed the issue #5550.Supports specifying port ranges, with automatic matching between listen and URI.

Command line:
avocado run examples/tests/passtest.py --status-server-disable-auto --status-server-listen 127.0.0.1:8888-9000 --status-server-uri 127.0.0.1:8888-9000

Use the testing program to occupy ports 8888/8889 for verification

logs:
...
2026-03-04 15:06:58,073 avocado.job job L0291 INFO | 'run.status_server_listen': '127.0.0.1:8888-9000',
2026-03-04 15:06:58,073 avocado.job job L0291 INFO | 'run.status_server_uri': '127.0.0.1:8888-9000',
...
2026-03-04 15:06:58,439 avocado.job server L0068 INFO | Status server listening on 127.0.0.1:8890
2026-03-04 15:06:58,740 avocado.job testlogs L0132 INFO | examples/tests/passtest.py:PassTest.test: STARTED
2026-03-04 15:06:58,948 avocado.job testlogs L0138 INFO | examples/tests/passtest.py:PassTest.test: PASS
...

Signed-off-by: xianglongfei xianglongfei@uniontech.com

@mr-avocado mr-avocado bot moved this to Review Requested in Default project Mar 4, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the flexibility of the status server's network configuration by introducing support for port ranges. Users can now specify a range of ports, and the system will intelligently select an available port within that range for both the server to listen on and for clients to connect to. This change addresses potential port conflicts and simplifies deployment in dynamic environments, ensuring a more robust and adaptable status server setup.

Highlights

  • Port Range Support: The status server now supports specifying port ranges (e.g., 127.0.0.1:8888-9000) for both listening and connecting. The system will automatically find and use an available port within the specified range.
  • Automatic Port Resolution: A new mechanism has been introduced to resolve a concrete port from a given range for the status server. This ensures that both the server and client tasks connect to the exact same, available port, maintaining consistency across configurations.
  • Improved Error Handling: Enhanced error handling for invalid port ranges and connection refusals, providing clearer feedback when a suitable port cannot be found or connected to.
  • Documentation Update: The man page has been updated to reflect the new port range functionality for --status-server-listen and --status-server-uri options.
Changelog
  • avocado/core/nrunner/task.py
    • Implemented support for connecting to a status server using a port range in the URI, with retry logic for ConnectionRefusedError.
    • Added validation for port range format and order (start <= end).
  • avocado/core/status/server.py
    • Added LOG_JOB import for logging server status.
    • Modified create_server to allow the status server to bind to an available port within a specified range, updating the internal URI accordingly.
    • Included validation for port range format and order (start <= end).
  • avocado/plugins/runner_nrunner.py
    • Imported the socket module for network operations.
    • Introduced _resolve_listen_address to pre-resolve a concrete port from a given range for the status server by attempting to bind a temporary socket.
    • Updated _create_status_server to use the resolved listen address and ensure status_server_listen and status_server_uri configurations are consistent with the chosen port.
  • man/avocado.rst
    • Updated documentation for --status-server-listen and --status-server-uri to include port range syntax and explanation.
Activity
  • The pull request was opened by xianglongfei-8888 to address issue nrunner should have a back up plan for when port 8888 is taken #5550.
  • The author provided a detailed description including command-line examples and log snippets demonstrating the new functionality.
  • No human review comments or additional commits have been made since the initial submission.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds a useful feature to support port ranges for the status server, which includes automatic matching between the listen and URI configurations. The implementation correctly finds an available port and updates the configuration for other components. However, my review identified a few areas for improvement. There is significant code duplication in parsing port ranges across multiple files, which would benefit from being refactored into a shared utility function. I also found duplicated logic for handling single-port versus port-range connections that could be simplified. More critically, there appears to be dead code for port range handling in StatusServer.create_server, as the port is resolved before the server is instantiated. Lastly, I've noted a minor typo in the documentation.

Comment on lines +32 to +62
if "-" in port_spec:
start_s, end_s = port_spec.split("-", 1)
try:
start = int(start_s)
end = int(end_s)
except ValueError as exc:
raise ValueError(
f"Invalid port range in status server URI: {self._uri}"
) from exc
if start > end:
raise ValueError(
f"Invalid port range (start > end) in status server URI: {self._uri}"
)
last_exc = None
for port in range(start, end + 1):
try:
self._server_task = await asyncio.start_server(
self.cb, host=host, port=port, limit=limit
)
# Update URI to the actual chosen port so that
# other components can rely on the concrete value.
self._uri = f"{host}:{port}"
LOG_JOB.info("Status server listening on %s", self._uri)
break
except OSError as exc:
last_exc = exc
if self._server_task is None:
# Re-raise the last failure or a generic error if none.
raise last_exc or OSError(
f"Could not bind status server to any port in range {start}-{end} on {host}"
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The logic for handling port ranges in create_server appears to be dead code. The _resolve_listen_address method in avocado/plugins/runner_nrunner.py already finds an available port from the range and resolves it to a concrete host:port address before StatusServer is instantiated. Therefore, StatusServer will always receive a URI with a single port, and this port range handling logic will never be executed. This adds unnecessary complexity and should be removed.

Comment on lines +57 to +100
if "-" in port_spec:
start_s, end_s = port_spec.split("-", 1)
try:
self._connection = socket.create_connection((host, port))
break
except ConnectionRefusedError as error:
LOG.warning(error)
time.sleep(1)
start = int(start_s)
end = int(end_s)
except ValueError as exc:
raise ValueError(
f"Invalid port range in status server URI: {self.uri}"
) from exc
if start > end:
raise ValueError(
f"Invalid port range (start > end) in status server URI: {self.uri}"
)
last_error = None
for port in range(start, end + 1):
for _ in range(600):
try:
self._connection = socket.create_connection((host, port))
break
except ConnectionRefusedError as error:
last_error = error
LOG.warning(error)
time.sleep(1)
if self._connection is not None:
break
else:
# If we could not connect to any port in the range even
# after retries, raise the last connection error (if any)
# or attempt one final connection to the last port so that
# the underlying exception is surfaced.
if last_error is not None:
raise last_error
self._connection = socket.create_connection((host, end))
else:
self._connection = socket.create_connection((host, port))
port = int(port_spec)
for _ in range(600):
try:
self._connection = socket.create_connection((host, port))
break
except ConnectionRefusedError as error:
LOG.warning(error)
time.sleep(1)
else:
self._connection = socket.create_connection((host, port))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is significant code duplication between the logic for handling a port range (lines 57-89) and a single port (lines 90-100). Both branches contain a retry loop for socket.create_connection. This can be refactored into a single piece of logic that iterates over a list of ports, which would contain either one or multiple ports. This would improve maintainability and readability.

            ports = []
            if "-" in port_spec:
                start_s, end_s = port_spec.split("-", 1)
                try:
                    start = int(start_s)
                    end = int(end_s)
                except ValueError as exc:
                    raise ValueError(
                        f"Invalid port range in status server URI: {self.uri}"
                    ) from exc
                if start > end:
                    raise ValueError(
                        f"Invalid port range (start > end) in status server URI: {self.uri}"
                    )
                ports = range(start, end + 1)
            else:
                try:
                    ports = [int(port_spec)]
                except ValueError as exc:
                    raise ValueError(
                        f"Invalid port in status server URI: {self.uri}"
                    ) from exc

            last_error = None
            last_port_tried = None
            for port in ports:
                last_port_tried = port
                for _ in range(600):
                    try:
                        self._connection = socket.create_connection((host, port))
                        break
                    except ConnectionRefusedError as error:
                        last_error = error
                        LOG.warning(error)
                        time.sleep(1)
                if self._connection is not None:
                    break
            else:
                if last_error is not None:
                    raise last_error
                # If we could not connect, attempt one final connection to the
                # last port so that the underlying exception is surfaced.
                if last_port_tried is not None:
                    self._connection = socket.create_connection((host, last_port_tried))

Comment on lines +231 to +242
start_s, end_s = port_spec.split("-", 1)
try:
start = int(start_s)
end = int(end_s)
except ValueError as exc:
raise JobError(
f"Invalid port range in status server URI: {listen}"
) from exc
if start > end:
raise JobError(
f"Invalid port range (start > end) in status server URI: {listen}"
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for parsing a port range from a URI string is duplicated across multiple files: here in _resolve_listen_address, in avocado/core/nrunner/task.py (_create_connection), and in avocado/core/status/server.py (create_server). This should be extracted into a common utility function to avoid code duplication and improve maintainability.

man/avocado.rst Outdated
chosen. This is only effective if
"status_server_auto" is disabled. If
"status_server_uri" is not set, the value from
"status_server_listen " will be used.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's a typo here. "status_server_listen " has a trailing space. It should be "status_server_listen".

Suggested change
"status_server_listen " will be used.
"status_server_listen" will be used.

@codecov
Copy link

codecov bot commented Mar 4, 2026

Codecov Report

❌ Patch coverage is 43.58974% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.65%. Comparing base (74b7379) to head (70c2233).
⚠️ Report is 63 commits behind head on master.

Files with missing lines Patch % Lines
avocado/core/status/server.py 42.85% 16 Missing ⚠️
avocado/plugins/runner_nrunner.py 45.45% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6280      +/-   ##
==========================================
+ Coverage   73.48%   73.65%   +0.17%     
==========================================
  Files         206      206              
  Lines       22494    22655     +161     
==========================================
+ Hits        16530    16687     +157     
- Misses       5964     5968       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Signed-off-by: xianglongfei <xianglongfei@uniontech.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done 114

Development

Successfully merging this pull request may close these issues.

1 participant