Skip to content

Commit

Permalink
✨ feat: Make test cli
Browse files Browse the repository at this point in the history
  • Loading branch information
raxhvl committed Nov 18, 2024
1 parent bdb4bcd commit 1a877dd
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 72 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ order_fixtures = "cli.order_fixtures:order_fixtures"
evm_bytes = "cli.evm_bytes:cli"
hasher = "cli.hasher:main"
env_init = "config.env:create_default_config"
make_test = "cli.make.cli:test"
make = "cli.make.cli:make"

[tool.setuptools.packages.find]
where = ["src"]
Expand Down
91 changes: 24 additions & 67 deletions src/cli/make/cli.py
Original file line number Diff line number Diff line change
@@ -1,78 +1,35 @@
"""
CLI interface for generating blockchain test scripts.
The `make` CLI streamlines the process of scaffolding tasks, such as generating new test files,
enabling developers to concentrate on the core aspects of specification testing.
It extracts a specified transaction and its required state from a blockchain network
using the transaction hash and generates a Python test script based on that information.
"""

import os
import jinja2
The module verifies the presence of a valid subcommand and calls the appropriate
function for the subcommand. If an invalid subcommand is present, it throws an error
and shows a list of valid subcommands. If no subcommand is present, it shows a list
of valid subcommands to choose from.
"""

from cli.input import input_select, input_text
import click

template_loader = jinja2.PackageLoader("cli.make")
template_env = jinja2.Environment(loader=template_loader, keep_trailing_newline=True)
from cli.make.commands import test


def test():
@click.group()
def make():
"""
Scaffold a test file from the command line interface (CLI).
This function guides the user through a series of prompts to generate a test file
for Ethereum execution specifications. The user is prompted to select the type of test,
the fork to use, and to provide the EIP number and name. Based on the inputs, a test file
is created in the appropriate directory with a rendered template.
Prompts:
- Choose the type of test to generate (State or Blockchain)
- Select the fork to use (Prague or Osaka)
- Enter the EIP number
- Enter the EIP name
The generated test file is saved in the following format:
`tests/{fork}/{eip_number}_{eip_name}/test_{eip_name}.py`
Example:
If the user selects "State" as the test type, "Prague" as the fork,
enters "1234" as the EIP number,
and "Sample EIP" as the EIP name, the generated file will be:
`tests/prague/1234_sample_eip/test_sample_eip.py`
The function uses Jinja2 templates to render the content of the test file.
Raises:
- FileNotFoundError: If the template file does not exist.
- IOError: If there is an error writing the file.
The `make` CLI command helps you get started with new writing tests.
"""
test_type = input_select(
"Choose the type of test to generate", choices=["State", "Blockchain"]
)
# TODO: Get forks from a config called `UPCOMING_FORKS`
fork = input_select("Select the fork to use", choices=["Prague", "Osaka"])
pass

eip_number = input_text("Enter the EIP number")

# TODO: Perhaps get the EIP name from the number using an API?
eip_name = input_text("Enter the EIP name")

file_name = f"test_{eip_name.lower().replace(' ', '_')}.py"

directory_path = f"tests/{fork.lower()}/{eip_number}-{eip_name.lower().replace(' ', '_')}"
file_path = f"{directory_path}/{file_name}"

# Create directories if they don't exist
os.makedirs(directory_path, exist_ok=True)

template = template_env.get_template(f"{test_type.lower()}_test.py.j2")
rendered_template = template.render(
fork=fork,
eip_number=eip_number,
eip_name=eip_name,
test_name=eip_name.lower().replace(" ", "_"),
)

with open(file_path, "w") as file:
file.write(rendered_template)

print(f"Test file created at: {file_path}")
"""
################################
|| ||
|| Command Registration ||
|| ||
################################
Register nested commands here. For more information, see Click documentation:
https://click.palletsprojects.com/en/8.0.x/commands/#nested-handling-and-contexts
"""
make.add_command(test)
9 changes: 9 additions & 0 deletions src/cli/make/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""
This subpackage holds subcommands for the make command. New subcommands must be created as
modules and exported from this package, then registered under the make command in
`cli.py`.
"""

from .test import test

__all__ = ["test"]
62 changes: 62 additions & 0 deletions src/cli/make/commands/quotes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""
This module contains a list of quotes related to system design.
"""

import random
import textwrap

make_something_great = [
"🎨 Simplicity is the ultimate sophistication. - Leonardo D.",
"🖌️ Simplicity is an acquired taste. - Katharine G.",
"💡 To create a memorable design you need to start with a thought that’s worth remembering."
" - Thomas M.",
"✏️ Designers are crazy and yet sane enough to know where to draw the line. - Benjamin W.",
"🌟 Creativity is piercing the mundane to find the marvelous. - Bill M.",
"🔍 Mistakes are the portals of discovery. - James J.",
"🧠 It’s extremely difficult to be simultaneously concerned with the end-user experience of"
" whatever it is that you’re building and the architecture of the program that delivers that"
"experience. - James H.",
"🧠 Good design is a lot like clear thinking made visual. - Edward T.",
"🚀 Innovation leads one to see the new in the old and distinguishes the ingenious from the"
" ingenuous. - Paul R.",
"🔮 The best way to predict the future is to invent it. - Alan K.",
"🌟 Perfection is achieved, not when there is nothing more to add, but when there is nothing"
" left to take away. - Antoine d.",
"📏 You can’t improve what you don’t measure. - Tom D.",
]


def wrap_quote(quote, width=80):
"""
Wraps the quote text to the given width.
"""
return textwrap.fill(quote, width=width)


def box_quote(quote):
"""
Returns a quote wrapped in a box with borders.
"""
# Wrap the quote first
wrapped_quote = wrap_quote(quote)

# Calculate the width of the box
box_width = max(len(line) for line in wrapped_quote.split("\n")) + 2 # +2 for side borders

# Create top and bottom border
top_bottom_border = "+" + "-" * (box_width) + "+"

# Create the sides of the box
lines = wrapped_quote.split("\n")
boxed_lines = [f"{line.ljust(box_width - 2)}" for line in lines]

# Return the full boxed quote
quote = "\n".join([top_bottom_border] + boxed_lines + [top_bottom_border])
return f"\n {quote} \n"


def get_quote():
"""
Returns a random inspirational quote related to system design formatted in a box.
"""
return box_quote(random.choice(make_something_great))
84 changes: 84 additions & 0 deletions src/cli/make/commands/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""
This module provides a CLI command to scaffold a test file.
The `test` command guides the user through a series of prompts to generate a test file
based on the selected test type, fork, EIP number, and EIP name. The generated test file
is saved in the appropriate directory with a rendered template using Jinja2.
"""

import os

import click
import jinja2

from cli.input import input_select, input_text

from .quotes import get_quote

template_loader = jinja2.PackageLoader("cli.make")
template_env = jinja2.Environment(loader=template_loader, keep_trailing_newline=True)


@click.command()
def test():
"""
Create a new specification test file for an EIP.
This function guides the user through a series of prompts to generate a test file
for Ethereum execution specifications. The user is prompted to select the type of test,
the fork to use, and to provide the EIP number and name. Based on the inputs, a test file
is created in the appropriate directory with a rendered template.
Prompts:
- Choose the type of test to generate (State or Blockchain)
- Select the fork to use (Prague or Osaka)
- Enter the EIP number
- Enter the EIP name
The generated test file is saved in the following format:
`tests/{fork}/eip{eip_number}_{eip_name}/test_{eip_name}.py`
Example:
If the user selects "State" as the test type, "Prague" as the fork,
enters "1234" as the EIP number,
and "Sample EIP" as the EIP name, the generated file will be:
`tests/prague/eip1234_sample_eip/test_sample_eip.py`
The function uses Jinja2 templates to render the content of the test file.
Raises:
- FileNotFoundError: If the template file does not exist.
- IOError: If there is an error writing the file.
"""
test_type = input_select(
"Choose the type of test to generate", choices=["State", "Blockchain"]
)
# TODO: Get forks from a config called `UPCOMING_FORKS`
fork = input_select("Select the fork to use", choices=["Prague", "Osaka"])

eip_number = input_text("Enter the EIP number").strip()

# TODO: Perhaps get the EIP name from the number using an API?
eip_name = input_text("Enter the EIP name").strip()

file_name = f"test_{eip_name.lower().replace(' ', '_')}.py"

directory_path = f"tests/{fork.lower()}/eip{eip_number}_{eip_name.lower().replace(' ', '_')}"
file_path = f"{directory_path}/{file_name}"

# Create directories if they don't exist
os.makedirs(directory_path, exist_ok=True)

template = template_env.get_template(f"{test_type.lower()}_test.py.j2")
rendered_template = template.render(
fork=fork,
eip_number=eip_number,
eip_name=eip_name,
test_name=eip_name.lower().replace(" ", "_"),
)

with open(file_path, "w") as file:
file.write(rendered_template)

print(f"\n 🎉 Success! Test file created at: {file_path}")
print(get_quote())
6 changes: 4 additions & 2 deletions src/cli/make/templates/blockchain_test.py.j2
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
A blockchain test for EIP-{{eip_number}} [fork: `{{fork}}`].
A blockchain test for [EIP-{{eip_number}} {{eip_name}}](https://eips.ethereum.org/EIPS/eip-{eip_number}}).
"""

import pytest
Expand All @@ -10,7 +10,9 @@ from ethereum_test_tools import Alloc, Environment, BlockchainTestFiller, Transa
@pytest.mark.valid_from("{{fork}}")
def test_{{test_name}}(blockchain_test: BlockchainTestFiller, pre: Alloc):
"""
Test type 1 transaction.
TODO: Enter a one-line test summary here.

TODO: (Optional) Enter a more detailed test function description here.
"""
env = Environment()

Expand Down
6 changes: 4 additions & 2 deletions src/cli/make/templates/state_test.py.j2
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
A state test for EIP-{{eip_number}} [fork: `{{fork}}`].
A state test for [EIP-{{eip_number}} {{eip_name}}](https://eips.ethereum.org/EIPS/eip-{eip_number}}).
"""

import pytest
Expand All @@ -10,7 +10,9 @@ from ethereum_test_tools import Alloc, Environment, StateTestFiller, Transaction
@pytest.mark.valid_from("{{fork}}")
def test_{{test_name}}(state_test: StateTestFiller, pre: Alloc):
"""
Test type 1 transaction.
TODO: Enter a one-line test summary here.

TODO: (Optional) Enter a more detailed test function description here.
"""
env = Environment()

Expand Down

0 comments on commit 1a877dd

Please sign in to comment.