-
Notifications
You must be signed in to change notification settings - Fork 452
New era of e2e Tests Bittensor #2743
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
basfroman
merged 35 commits into
staging
from
feat/roman/test-run-tests-without-building
Mar 17, 2025
Merged
Changes from 29 commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
ca261c0
add BUILD_BINARY=0
0074c9b
replace devnet-ready to devnet-ready-with-nodes
0511b9f
Merge branch 'staging' into feat/roman/test-run-tests-without-building
basfroman a810aae
trigger actions
de812b9
add debug
e66c9ed
return proper runner
99390ba
get-latest-artifact-id
83ef99f
try tests with compatible architecture
a04018c
use the same runner
3210071
debug
1ce9578
debug file
b671655
sudo
f1f0898
more debug info
d044f89
more debug info
3007f67
more debug info
336af8e
more debug info
6caa770
Merge branch 'staging' into feat/roman/test-run-tests-without-building
bb2d795
ruff
1538152
update e2e tests workflow
99aab1b
fix
8841166
add reqs
f42de89
try improvement
c3595ea
add needs
93d8b1c
remove venv job
096a312
fix
85067d2
test time with uv
049aeb8
test time with uv
a594e73
remove debug file
c117432
clean up
0ac6ffc
fix comments + new logic
9a2c531
update README.md
ef87986
refactor
5022a5f
ruff
a23f2cd
fix except
e3d836a
Merge branch 'staging' into feat/roman/test-run-tests-without-building
ibraheem-abe File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,27 +1,82 @@ | ||
| import os | ||
| import re | ||
| import shutil | ||
| import shlex | ||
| import signal | ||
| import subprocess | ||
| import time | ||
| import sys | ||
| import threading | ||
| import time | ||
| from bittensor.utils.btlogging import logging | ||
|
|
||
| import pytest | ||
| from async_substrate_interface import SubstrateInterface | ||
|
|
||
| from bittensor.core.async_subtensor import AsyncSubtensor | ||
| from bittensor.core.subtensor import Subtensor | ||
| from bittensor.utils.btlogging import logging | ||
| from tests.e2e_tests.utils.e2e_test_utils import ( | ||
| Templates, | ||
| setup_wallet, | ||
| ) | ||
|
|
||
|
|
||
| # Fixture for setting up and tearing down a localnet.sh chain between tests | ||
| def wait_for_node_start(process, timestamp=None): | ||
| """Waits for node to start in the docker.""" | ||
| while True: | ||
| line = process.stdout.readline() | ||
| if not line: | ||
| break | ||
|
|
||
| timestamp = timestamp or int(time.time()) | ||
| print(line.strip()) | ||
| # 10 min as timeout | ||
| if int(time.time()) - timestamp > 20 * 30: | ||
| print("Subtensor not started in time") | ||
| raise TimeoutError | ||
|
|
||
| pattern = re.compile(r"Imported #1") | ||
| if pattern.search(line): | ||
| print("Node started!") | ||
| break | ||
|
|
||
| # Start a background reader after pattern is found | ||
| # To prevent the buffer filling up | ||
| def read_output(): | ||
| while True: | ||
| if not process.stdout.readline(): | ||
| break | ||
|
|
||
| reader_thread = threading.Thread(target=read_output, daemon=True) | ||
| reader_thread.start() | ||
|
|
||
|
|
||
| @pytest.fixture(scope="function") | ||
| def local_chain(request): | ||
| param = request.param if hasattr(request, "param") else None | ||
| """Determines whether to run the localnet.sh script in a subprocess or a Docker container.""" | ||
| args = request.param if hasattr(request, "param") else None | ||
| params = "" if args is None else f"{args}" | ||
| if shutil.which("docker"): | ||
| yield from docker_runner(params) | ||
| return | ||
|
|
||
| if sys.platform.startswith("linux"): | ||
| docker_commend = ( | ||
| "Install docker with command " | ||
| "[blue]sudo apt-get update && sudo apt-get install docker.io -y[/blue]" | ||
| ) | ||
| elif sys.platform == "darwin": | ||
| docker_commend = "Install docker with command [blue]brew install docker[/blue]" | ||
| else: | ||
| docker_commend = "[blue]Unknown OS, install Docker manually: https://docs.docker.com/get-docker/[/blue]" | ||
|
|
||
| logging.warning("Docker not found in the operating system!") | ||
| logging.warning(docker_commend) | ||
| logging.warning("Tests are run in legacy mode.") | ||
| yield from legacy_runner(request) | ||
|
|
||
|
|
||
| def legacy_runner(params): | ||
| """Runs the localnet.sh script in a subprocess and waits for it to start.""" | ||
| # Get the environment variable for the script path | ||
| script_path = os.getenv("LOCALNET_SH_PATH") | ||
|
|
||
|
|
@@ -31,41 +86,11 @@ def local_chain(request): | |
| pytest.skip("LOCALNET_SH_PATH environment variable is not set.") | ||
|
|
||
| # Check if param is None, and handle it accordingly | ||
| args = "" if param is None else f"{param}" | ||
| args = "" if params is None else f"{params}" | ||
|
|
||
| # Compile commands to send to process | ||
| cmds = shlex.split(f"{script_path} {args}") | ||
|
|
||
| # Pattern match indicates node is compiled and ready | ||
| pattern = re.compile(r"Imported #1") | ||
| timestamp = int(time.time()) | ||
|
|
||
| def wait_for_node_start(process, pattern): | ||
| while True: | ||
| line = process.stdout.readline() | ||
| if not line: | ||
| break | ||
|
|
||
| print(line.strip()) | ||
| # 10 min as timeout | ||
| if int(time.time()) - timestamp > 20 * 60: | ||
| print("Subtensor not started in time") | ||
| raise TimeoutError | ||
| if pattern.search(line): | ||
| print("Node started!") | ||
| break | ||
|
|
||
| # Start a background reader after pattern is found | ||
| # To prevent the buffer filling up | ||
| def read_output(): | ||
| while True: | ||
| line = process.stdout.readline() | ||
| if not line: | ||
| break | ||
|
|
||
| reader_thread = threading.Thread(target=read_output, daemon=True) | ||
| reader_thread.start() | ||
|
|
||
| with subprocess.Popen( | ||
| cmds, | ||
| start_new_session=True, | ||
|
|
@@ -74,7 +99,7 @@ def read_output(): | |
| text=True, | ||
| ) as process: | ||
| try: | ||
| wait_for_node_start(process, pattern) | ||
| wait_for_node_start(process) | ||
| except TimeoutError: | ||
| raise | ||
| else: | ||
|
|
@@ -91,6 +116,59 @@ def read_output(): | |
| process.wait() | ||
|
|
||
|
|
||
| def docker_runner(params): | ||
| """Starts a Docker container before tests and gracefully terminates it after.""" | ||
|
|
||
| container_name = f"test_local_chain_{str(time.time()).replace(".", "_")}" | ||
| image_name = "ghcr.io/opentensor/subtensor-localnet:latest" | ||
|
|
||
| # Command to start container | ||
| cmds = [ | ||
| "docker", | ||
| "run", | ||
| "--rm", | ||
| "--name", | ||
| container_name, | ||
| "-p", | ||
| "9944:9944", | ||
| "-p", | ||
| "9945:9945", | ||
| image_name, | ||
| params, | ||
| ] | ||
|
|
||
| # Start container | ||
| with subprocess.Popen( | ||
| cmds, | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.PIPE, | ||
| text=True, | ||
| start_new_session=True, | ||
| ) as process: | ||
| try: | ||
| try: | ||
| wait_for_node_start(process, int(time.time())) | ||
| except TimeoutError: | ||
| raise | ||
|
|
||
| result = subprocess.run( | ||
| ["docker", "ps", "-q", "-f", f"name={container_name}"], | ||
| capture_output=True, | ||
| text=True, | ||
| ) | ||
| if not result.stdout.strip(): | ||
| raise RuntimeError("Docker container failed to start.") | ||
|
|
||
| yield SubstrateInterface(url="ws://127.0.0.1:9944") | ||
|
||
|
|
||
| finally: | ||
| try: | ||
| subprocess.run(["docker", "kill", container_name]) | ||
| process.wait() | ||
| except subprocess.TimeoutExpired: | ||
| os.killpg(os.getpgid(process.pid), signal.SIGKILL) | ||
|
|
||
|
|
||
| @pytest.fixture(scope="session") | ||
| def templates(): | ||
| with Templates() as templates: | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only works for
apt-based distros. May want to adjust the wording here.