Skip to content
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

Create fast bootstrapped container & hook into pytest correctly #21

Merged
merged 1 commit into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,6 @@ include = ["src/leap"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project.entry-points."pytest11"]
leap = "leap.fixtures"
2 changes: 2 additions & 0 deletions src/leap/cleos.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class CLEOS:
def __init__(
self,
endpoint: str = 'http://127.0.0.1:8888',
node_dir: Path | None = None,
logger = None
):
if logger is None:
Expand All @@ -52,6 +53,7 @@ def __init__(
self.logger = logger

self.endpoint = endpoint
self.node_dir = node_dir

self.keys: dict[str, str] = {}
self.private_keys: dict[str, str] = {}
Expand Down
135 changes: 117 additions & 18 deletions src/leap/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@


DEFAULT_NODEOS_REPO = 'guilledk/py-leap'
DEFAULT_NODEOS_IMAGE = 'leap-4.0.4'
DEFAULT_NODEOS_IMAGE = 'leap-5.0.3'


def default_nodeos_image():
Expand All @@ -37,6 +37,91 @@ def maybe_get_marker(request, mark_name: str, field: str, default):
return getattr(mark, field)


@contextmanager
def open_test_nodeos(request, tmp_path_factory):
tmp_path = tmp_path_factory.getbasetemp() / request.node.name
leap_path = tmp_path / 'leap'
leap_path.mkdir(parents=True, exist_ok=True)
leap_path = leap_path.resolve()

logging.info(f'created tmp path at {leap_path}')

dclient = docker.from_env()

container_img = default_nodeos_image() + '-bs'
logging.info(f'launching {container_img} container...')

http_port = get_free_port()
cmd = ['nodeos', '-e', '-p', 'eosio', '--config-dir', '/', '--data-dir', '/data']
cmd += [
'>>', '/root/nodeos.log', '2>&1'
]

container_cmd = ['/bin/bash', '-c', ' '.join(cmd)]

vtestnet = get_container(
dclient,
container_img,
force_unique=True,
name=f'{tmp_path.name}-leap',
detach=True,
remove=True,
ports={'8888/tcp': http_port},
mounts=[Mount('/root', str(leap_path), 'bind')],
command=container_cmd
)
cleos = CLEOS(f'http://127.0.0.1:{http_port}', node_dir=leap_path)

# preload apis
rcleos = CLEOS('https://testnet.telos.net')
for account_name in ['eosio', 'eosio.token']:
abi = rcleos.get_abi(account_name)
cleos.load_abi(account_name, abi)

# load keys
ec, out = vtestnet.exec_run('cat /keys.json')
keys = json.loads(out.decode('utf-8'))

for account, keys in keys.items():
priv, _pub = keys
cleos.import_key(account, priv)

did_nodeos_launch = False

try:
cleos.import_key('eosio', '5Jr65kdYmn33C3UabzhmWDm2PuqbRfPuDStts3ZFNSBLM7TqaiL')
cleos.wait_blocks(1)

did_nodeos_launch = True

yield cleos

finally:
if did_nodeos_launch:
logging.info(f'to see nodeos logs: \"less {leap_path}/nodeos.log\"')

else:
process = subprocess.run(
['cat', str(leap_path / 'nodeos.log')],
text=True, capture_output=True
)
logging.error('seems nodeos didn\'t launch? showing logs...')
logging.error(process.stdout)

if vtestnet is not None:
try:
vtestnet.exec_run('pkill -f nodeos')
vtestnet.wait(timeout=120)
vtestnet.kill(signal='SIGTERM')
vtestnet.wait(timeout=20)

except docker.errors.NotFound:
...

except docker.errors.APIError:
...


@contextmanager
def bootstrap_test_nodeos(request, tmp_path_factory):
tmp_path = tmp_path_factory.getbasetemp() / request.node.name
Expand All @@ -60,7 +145,7 @@ def bootstrap_test_nodeos(request, tmp_path_factory):
container_img = default_nodeos_image()
logging.info(f'launching {container_img} container...')

cmd = ['nodeos', '-e', '-p', 'eosio', '--config-dir', '/root']
cmd = ['nodeos', '-e', '-p', 'eosio', '--config-dir', '/root', '--data-dir', '/root/data']

for plugin in [
'net_plugin',
Expand All @@ -79,7 +164,7 @@ def bootstrap_test_nodeos(request, tmp_path_factory):
if randomize:
priv, pub = gen_key_pair()
else:
priv, pub = ('5KU4gWTqUWHHh2EhjcK73eYF4T8cWytNkv38qtg4tXtF8iphTZy', 'EOS6FQkekshKwynMNxQLJjXFFLLekiDNYXXK2bAukVVrkhqdkusoj')
priv, pub = ('5Jr65kdYmn33C3UabzhmWDm2PuqbRfPuDStts3ZFNSBLM7TqaiL', 'EOS5GnobZ231eekYUJHGTcmy2qve1K23r5jSFQbMfwWTtPB7mFZ1L')

cmd += ['--signature-provider', f'{pub}=KEY:{priv}']

Expand Down Expand Up @@ -141,7 +226,7 @@ def bootstrap_test_nodeos(request, tmp_path_factory):
download_location.mkdir(exist_ok=True, parents=True)


cleos = CLEOS(f'http://127.0.0.1:{http_port}')
cleos = CLEOS(f'http://127.0.0.1:{http_port}', node_dir=leap_path)
rcleos = CLEOS('https://testnet.telos.net')

def maybe_download_contract(
Expand Down Expand Up @@ -197,25 +282,39 @@ def maybe_download_contract(
yield cleos

finally:
try:
if did_nodeos_launch:
logging.info(f'to see nodeos logs: \"less {leap_path}/nodeos.log\"')
if did_nodeos_launch:
logging.info(f'to see nodeos logs: \"less {leap_path}/nodeos.log\"')

else:
process = subprocess.run(
['cat', str(leap_path / 'nodeos.log')],
text=True, capture_output=True
)
logging.error('seems nodeos didn\'t launch? showing logs...')
logging.error(process.stdout)

else:
process = subprocess.run(
['cat', str(leap_path / 'nodeos.log')],
text=True, capture_output=True
)
logging.error('seems nodeos didn\'t launch? showing logs...')
logging.error(process.stdout)
vtestnet.exec_run('chmod 777 /root')

vtestnet.kill()
if vtestnet is not None:
try:
vtestnet.exec_run('pkill -f nodeos')
vtestnet.wait(timeout=120)
vtestnet.kill(signal='SIGTERM')
vtestnet.wait(timeout=20)

except docker.errors.NotFound:
...
except docker.errors.NotFound:
...

except docker.errors.APIError:
...

@pytest.fixture()

@pytest.fixture(scope='module')
def cleos(request, tmp_path_factory):
with bootstrap_test_nodeos(request, tmp_path_factory) as cleos:
yield cleos

@pytest.fixture(scope='module')
def cleos_bs(request, tmp_path_factory):
with open_test_nodeos(request, tmp_path_factory) as cleos:
yield cleos
7 changes: 1 addition & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,10 @@ def nodeless_cleos():
yield CLEOS()


@pytest.fixture(scope='module')
def cleos(request, tmp_path_factory):
with bootstrap_test_nodeos(request, tmp_path_factory) as cleos:
yield cleos


@pytest.fixture(scope='module')
def cleos_w_bootstrap(request, tmp_path_factory):
request.applymarker(pytest.mark.bootstrap(True))
request.applymarker(pytest.mark.randomize(False))
with bootstrap_test_nodeos(request, tmp_path_factory) as cleos:
yield cleos

Expand Down
13 changes: 13 additions & 0 deletions tests/test_generate_preload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import json

def test_generate(cleos_w_bootstrap):
cleos = cleos_w_bootstrap

with open(cleos.node_dir / 'keys.json', 'w+') as key_file:
accounts = list(cleos.private_keys.keys())
key_file.write(json.dumps({
account: (cleos.private_keys[account], cleos.keys[account])
for account in accounts
}, indent=4))

cleos.wait_blocks(1)
2 changes: 1 addition & 1 deletion tests/test_rscdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def test_wait_start(cleos_w_indextest):
print("eosio: ", cleos.private_keys['eosio'])
print("cindextest: ", cleos.private_keys['cindextest'])
print("rindextest: ", cleos.private_keys['rindextest'])
breakpoint()
# breakpoint()


def test_load_storage_only(cleos_w_indextest):
Expand Down
44 changes: 22 additions & 22 deletions tests/test_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from leap.errors import TransactionPushError


def test_create(cleos_w_bootstrap):
cleos = cleos_w_bootstrap
def test_create(cleos_bs):
cleos = cleos_bs
creator = cleos.new_account()
sym = random_token_symbol()
max_supply = f'1000.000 {sym}'
Expand All @@ -21,8 +21,8 @@ def test_create(cleos_w_bootstrap):
assert tkn_stats['issuer'] == creator


def test_create_negative_supply(cleos_w_bootstrap):
cleos = cleos_w_bootstrap
def test_create_negative_supply(cleos_bs):
cleos = cleos_bs
creator = cleos.new_account()

with pytest.raises(TransactionPushError) as err:
Expand All @@ -32,8 +32,8 @@ def test_create_negative_supply(cleos_w_bootstrap):
assert 'max-supply must be positive' in str(err)


def test_symbol_exists(cleos_w_bootstrap):
cleos = cleos_w_bootstrap
def test_symbol_exists(cleos_bs):
cleos = cleos_bs
creator = cleos.new_account()
sym = random_token_symbol()
max_supply = f'1000.000 {sym}'
Expand All @@ -48,8 +48,8 @@ def test_symbol_exists(cleos_w_bootstrap):
assert 'token with symbol already exists' in str(err)


def test_create_max_possible(cleos_w_bootstrap):
cleos = cleos_w_bootstrap
def test_create_max_possible(cleos_bs):
cleos = cleos_bs
creator = cleos.new_account()
amount = (1 << 62) - 1
sym = random_token_symbol()
Expand All @@ -64,8 +64,8 @@ def test_create_max_possible(cleos_w_bootstrap):
assert tkn_stats['issuer'] == creator


def test_create_max_possible_plus_one(cleos_w_bootstrap):
cleos = cleos_w_bootstrap
def test_create_max_possible_plus_one(cleos_bs):
cleos = cleos_bs
creator = cleos.new_account()
amount = (1 << 62)
sym = random_token_symbol()
Expand All @@ -76,8 +76,8 @@ def test_create_max_possible_plus_one(cleos_w_bootstrap):

assert 'invalid supply' in str(err)

def test_create_max_decimals(cleos_w_bootstrap):
cleos = cleos_w_bootstrap
def test_create_max_decimals(cleos_bs):
cleos = cleos_bs
creator = cleos.new_account()
amount = 1
decimals = 18
Expand All @@ -94,8 +94,8 @@ def test_create_max_decimals(cleos_w_bootstrap):
assert tkn_stats['issuer'] == creator


def test_issue(cleos_w_bootstrap):
cleos = cleos_w_bootstrap
def test_issue(cleos_bs):
cleos = cleos_bs
creator = cleos.new_account()
sym = random_token_symbol()
max_supply = f'1000.000 {sym}'
Expand Down Expand Up @@ -132,8 +132,8 @@ def test_issue(cleos_w_bootstrap):
cleos.issue_token(creator, issued, 'hola')


def test_retire(cleos_w_bootstrap):
cleos = cleos_w_bootstrap
def test_retire(cleos_bs):
cleos = cleos_bs
creator = cleos.new_account()
sym = random_token_symbol()
max_supply = f'1000.000 {sym}'
Expand Down Expand Up @@ -207,8 +207,8 @@ def test_retire(cleos_w_bootstrap):
assert 'overdrawn balance' in str(err)


def test_transfer(cleos_w_bootstrap):
cleos = cleos_w_bootstrap
def test_transfer(cleos_bs):
cleos = cleos_bs
creator = cleos.new_account()
sym = random_token_symbol()
max_supply = f'1000 {sym}'
Expand Down Expand Up @@ -249,8 +249,8 @@ def test_transfer(cleos_w_bootstrap):
assert 'must transfer positive quantity' in str(err)


def test_open(cleos_w_bootstrap):
cleos = cleos_w_bootstrap
def test_open(cleos_bs):
cleos = cleos_bs
creator = cleos.new_account()
sym = random_token_symbol()
max_supply = f'1000 {sym}'
Expand Down Expand Up @@ -306,8 +306,8 @@ def test_open(cleos_w_bootstrap):
assert 'symbol precision mismatch' in str(err)


def test_close(cleos_w_bootstrap):
cleos = cleos_w_bootstrap
def test_close(cleos_bs):
cleos = cleos_bs
creator = cleos.new_account()
sym = random_token_symbol()
max_supply = f'1000 {sym}'
Expand Down