diff --git a/moccasin/commands/install.py b/moccasin/commands/install.py index b9e4e95..b5a17fe 100644 --- a/moccasin/commands/install.py +++ b/moccasin/commands/install.py @@ -71,7 +71,16 @@ def mox_install( github_requirements.append(requirement) else: pip_requirements.append(requirement) + + # Get dependencies install path and create it if it doesn't exist + # @dev allows to avoid vyper compiler error when missing one dir install_path: Path = config.get_base_dependencies_install_path() + install_path.joinpath(PYPI).mkdir(exist_ok=True, parents=True) + install_path.joinpath(GITHUB).mkdir(exist_ok=True, parents=True) + + # @dev in case of fresh install, dependencies might be ordered differently + # since we install pip packages first and github packages later + # @dev see _dependency_utils._write_new_dependencies if len(pip_requirements) > 0: _pip_installs( pip_requirements, install_path.joinpath(PYPI), quiet, override_logger diff --git a/tests/cli/test_cli_compile.py b/tests/cli/test_cli_compile.py index debd8bd..b6413e9 100644 --- a/tests/cli/test_cli_compile.py +++ b/tests/cli/test_cli_compile.py @@ -2,6 +2,24 @@ import subprocess from pathlib import Path +import pytest +import tomli_w +from packaging.requirements import Requirement + +from moccasin.config import Config +from tests.constants import ( + LIB_GH_PATH, + LIB_PIP_PATH, + MOCCASIN_LIB_NAME, + MOCCASIN_TOML, + PIP_PACKAGE_NAME, + VERSION, +) +from tests.utils.helpers import ( + get_temp_versions_toml_gh, + rewrite_temp_moccasin_toml_dependencies, +) + EXPECTED_HELP_TEXT = "Vyper compiler" @@ -9,9 +27,9 @@ def test_compile_help(mox_path): result = subprocess.run( [mox_path, "compile", "-h"], check=True, capture_output=True, text=True ) - assert ( - EXPECTED_HELP_TEXT in result.stdout - ), "Help output does not contain expected text" + assert EXPECTED_HELP_TEXT in result.stdout, ( + "Help output does not contain expected text" + ) assert result.returncode == 0 @@ -19,14 +37,17 @@ def test_build_help(mox_path): result = subprocess.run( [mox_path, "build", "-h"], check=True, capture_output=True, text=True ) - assert ( - EXPECTED_HELP_TEXT in result.stdout - ), "Help output does not contain expected text" + assert EXPECTED_HELP_TEXT in result.stdout, ( + "Help output does not contain expected text" + ) assert result.returncode == 0 def test_compile_alias_build_project( - complex_temp_path, complex_cleanup_out_folder, mox_path + complex_temp_path, + complex_cleanup_out_folder, + complex_cleanup_dependencies_folder, + mox_path, ): current_dir = Path.cwd() try: @@ -36,7 +57,22 @@ def test_compile_alias_build_project( ) finally: os.chdir(current_dir) + # Count the number of contracts in the contracts/ directory + # @dev avoid interfaces folder + contract_dir = complex_temp_path.joinpath("contracts") + contract_count = sum( + [ + len(files) + for root, _, files in os.walk(contract_dir) + if "interfaces" not in root + ] + ) + + assert complex_temp_path.joinpath().exists() + assert "Running compile command" in result.stderr + assert f"Compiling {contract_count} contracts to build/..." in result.stderr + assert "Done compiling project!" in result.stderr assert result.returncode == 0 @@ -45,12 +81,99 @@ def test_compile_one(complex_temp_path, complex_cleanup_out_folder, mox_path): try: os.chdir(current_dir.joinpath(complex_temp_path)) result = subprocess.run( - [mox_path, "build", "BuyMeACoffee.vy"], + [mox_path, "build", "BuyMeACoffee.vy", "--no-install"], check=True, capture_output=True, text=True, ) finally: os.chdir(current_dir) + + assert not complex_temp_path.joinpath(LIB_GH_PATH).exists() + assert not complex_temp_path.joinpath(LIB_PIP_PATH).exists() assert "Done compiling BuyMeACoffee" in result.stderr assert result.returncode == 0 + + +# @dev test adapted to the ordering of dependencies +@pytest.mark.parametrize( + "cli_args, rewrite_dependencies, expected_lib_path, expected_pip_deps, expected_gh_deps, expected_gh_versions", + [ + # --no-install should skip package installation + (["BuyMeACoffee.vy", "--no-install"], [], False, ["snekmate==0.1.0"], [], None), + # Default behavior - installs dependencies + ( + ["BuyMeACoffee.vy"], + [ + "PatrickAlphaC/test_repo", + f"{PIP_PACKAGE_NAME}>={VERSION}", + f"{MOCCASIN_LIB_NAME}==0.3.6", + ], + True, + [f"{PIP_PACKAGE_NAME}>={VERSION}", f"{MOCCASIN_LIB_NAME}==0.3.6"], + ["PatrickAlphaC/test_repo"], + {"patrickalphac/test_repo": "0.1.1"}, + ), + # Change compiled file + (["MyTokenPyPI.vy"], [], True, ["snekmate==0.1.0"], [], None), + ], +) +def test_compile_with_flags( + complex_temp_path, + complex_cleanup_out_folder, + complex_cleanup_dependencies_folder, + mox_path, + cli_args, + rewrite_dependencies, + expected_lib_path, + expected_pip_deps, + expected_gh_deps, + expected_gh_versions, +): + current_dir = Path.cwd() + old_moccasin_toml = rewrite_temp_moccasin_toml_dependencies( + complex_temp_path, rewrite_dependencies + ) + + try: + os.chdir(current_dir.joinpath(complex_temp_path)) + base_args = [mox_path, "build"] + result = subprocess.run( + base_args + cli_args, check=True, capture_output=True, text=True + ) + finally: + os.chdir(current_dir) + + assert complex_temp_path.joinpath(MOCCASIN_TOML).exists() + + gh_dir_path = complex_temp_path.joinpath(LIB_GH_PATH) + pip_dir_path = complex_temp_path.joinpath(LIB_PIP_PATH) + assert gh_dir_path.exists() == expected_lib_path + assert pip_dir_path.exists() == expected_lib_path + + for dep in expected_pip_deps: + pip_requirement = Requirement(dep) + assert pip_dir_path.joinpath(pip_requirement.name).exists() == expected_lib_path + if expected_gh_deps: + for dep in expected_gh_deps: + assert ( + gh_dir_path.joinpath(dep.lower().split("@")[0]).exists() + == expected_lib_path + ) + + # Verify config state if versions are expected + project_root: Path = Config.find_project_root(complex_temp_path) + config = Config(project_root) + assert config.dependencies == expected_pip_deps + expected_gh_deps + + # Verify gh versions file contents + if expected_gh_versions: + github_versions = get_temp_versions_toml_gh(complex_temp_path) + assert github_versions == expected_gh_versions + + assert f"Done compiling {cli_args[0].replace('.vy', '')}" in result.stderr + assert result.returncode == 0 + + # Reset toml to the original for next test + with open(complex_temp_path.joinpath(MOCCASIN_TOML), "wb") as f: + tomli_w.dump(old_moccasin_toml, f) diff --git a/tests/cli/test_cli_deploy.py b/tests/cli/test_cli_deploy.py index 573f32d..e39b607 100644 --- a/tests/cli/test_cli_deploy.py +++ b/tests/cli/test_cli_deploy.py @@ -2,6 +2,24 @@ import subprocess from pathlib import Path +import pytest +import tomli_w +from packaging.requirements import Requirement + +from moccasin.config import Config +from tests.constants import ( + LIB_GH_PATH, + LIB_PIP_PATH, + MOCCASIN_LIB_NAME, + MOCCASIN_TOML, + PIP_PACKAGE_NAME, + VERSION, +) +from tests.utils.helpers import ( + get_temp_versions_toml_gh, + rewrite_temp_moccasin_toml_dependencies, +) + # -------------------------------------------------------------- # WITHOUT ANVIL @@ -11,7 +29,7 @@ def test_deploy_price_feed_pyevm(mox_path, complex_temp_path, complex_project_co try: os.chdir(complex_temp_path) result = subprocess.run( - [mox_path, "deploy", "price_feed"], + [mox_path, "deploy", "price_feed", "--no-install"], check=True, capture_output=True, text=True, @@ -21,6 +39,89 @@ def test_deploy_price_feed_pyevm(mox_path, complex_temp_path, complex_project_co assert "Deployed contract price_feed on pyevm to" in result.stderr +# @dev test adapted to the ordering of dependencies +@pytest.mark.parametrize( + "cli_args, rewrite_dependencies, expected_lib_path, expected_pip_deps, expected_gh_deps, expected_gh_versions", + [ + # --no-install should skip package installation + (["price_feed", "--no-install"], [], False, ["snekmate==0.1.0"], [], None), + # Default behavior - installs dependencies + ( + ["price_feed"], + [ + "PatrickAlphaC/test_repo", + f"{PIP_PACKAGE_NAME}>={VERSION}", + f"{MOCCASIN_LIB_NAME}==0.3.6", + ], + True, + [f"{PIP_PACKAGE_NAME}>={VERSION}", f"{MOCCASIN_LIB_NAME}==0.3.6"], + ["PatrickAlphaC/test_repo"], + {"patrickalphac/test_repo": "0.1.1"}, + ), + # Change compiled file + (["price_feed"], [], True, ["snekmate==0.1.0"], [], None), + ], +) +def test_deploy_price_feed_pyevm_with_flags( + complex_temp_path, + complex_cleanup_out_folder, + complex_cleanup_dependencies_folder, + mox_path, + cli_args, + rewrite_dependencies, + expected_lib_path, + expected_pip_deps, + expected_gh_deps, + expected_gh_versions, +): + current_dir = Path.cwd() + old_moccasin_toml = rewrite_temp_moccasin_toml_dependencies( + complex_temp_path, rewrite_dependencies + ) + + try: + os.chdir(current_dir.joinpath(complex_temp_path)) + base_args = [mox_path, "deploy"] + result = subprocess.run( + base_args + cli_args, check=True, capture_output=True, text=True + ) + finally: + os.chdir(current_dir) + + assert complex_temp_path.joinpath(MOCCASIN_TOML).exists() + + gh_dir_path = complex_temp_path.joinpath(LIB_GH_PATH) + pip_dir_path = complex_temp_path.joinpath(LIB_PIP_PATH) + assert gh_dir_path.exists() == expected_lib_path + assert pip_dir_path.exists() == expected_lib_path + + for dep in expected_pip_deps: + pip_requirement = Requirement(dep) + assert pip_dir_path.joinpath(pip_requirement.name).exists() == expected_lib_path + if expected_gh_deps: + for dep in expected_gh_deps: + assert ( + gh_dir_path.joinpath(dep.lower().split("@")[0]).exists() + == expected_lib_path + ) + + # Verify config state if versions are expected + project_root: Path = Config.find_project_root(complex_temp_path) + config = Config(project_root) + assert config.dependencies == expected_pip_deps + expected_gh_deps + + # Verify gh versions file contents + if expected_gh_versions: + github_versions = get_temp_versions_toml_gh(complex_temp_path) + assert github_versions == expected_gh_versions + + assert "Deployed contract price_feed on pyevm to" in result.stderr + + # Reset toml to the original for next test + with open(complex_temp_path.joinpath(MOCCASIN_TOML), "wb") as f: + tomli_w.dump(old_moccasin_toml, f) + + # -------------------------------------------------------------- # WITH ANVIL # -------------------------------------------------------------- diff --git a/tests/cli/test_cli_install.py b/tests/cli/test_cli_install.py index d523232..6f50056 100644 --- a/tests/cli/test_cli_install.py +++ b/tests/cli/test_cli_install.py @@ -2,8 +2,25 @@ import subprocess from pathlib import Path +import pytest -def test_run_help(mox_path, installation_cleanup_dependencies, installation_temp_path): +from moccasin.config import Config +from tests.constants import ( + GITHUB_PACKAGE_NAME, + LIB_GH_PATH, + LIB_PIP_PATH, + MOCCASIN_LIB_NAME, + MOCCASIN_TOML, + NEW_VERSION, + PATRICK_PACKAGE_NAME, + PIP_PACKAGE_NAME, + VERSION, + VERSIONS_TOML, +) +from tests.utils.helpers import get_temp_versions_toml_gh + + +def test_run_help(mox_path, installation_temp_path): current_dir = Path.cwd() try: os.chdir(installation_temp_path) @@ -13,3 +30,228 @@ def test_run_help(mox_path, installation_cleanup_dependencies, installation_temp finally: os.chdir(current_dir) assert "Moccasin CLI install" in result.stdout + + +def test_run_install_no_dependencies( + mox_path, + installation_cleanup_dependencies, + installation_temp_path: Path, + installation_remove_dependencies, +): + current_dir = Path.cwd() + try: + os.chdir(installation_temp_path) + result = subprocess.run( + [mox_path, "install"], check=True, capture_output=True, text=True + ) + finally: + os.chdir(current_dir) + + assert "No dependencies to install" in result.stderr + + assert installation_temp_path.joinpath(MOCCASIN_TOML).exists() + assert not installation_temp_path.joinpath(LIB_GH_PATH).exists() + assert not installation_temp_path.joinpath(LIB_PIP_PATH).exists() + + +def test_run_install_only_pip_dependencies( + mox_path, + installation_cleanup_dependencies, + installation_temp_path: Path, + installation_keep_pip_dependencies, +): + current_dir = Path.cwd() + try: + os.chdir(installation_temp_path) + result = subprocess.run( + [mox_path, "install"], check=True, capture_output=True, text=True + ) + finally: + os.chdir(current_dir) + + assert "Installing 2 pip packages..." in result.stderr + + assert installation_temp_path.joinpath(MOCCASIN_TOML).exists() + assert installation_temp_path.joinpath(LIB_GH_PATH).exists() + assert not any(installation_temp_path.joinpath(LIB_GH_PATH).iterdir()) + + assert installation_temp_path.joinpath(LIB_PIP_PATH).exists() + assert any(installation_temp_path.joinpath(LIB_PIP_PATH).iterdir()) + + +def test_run_install_only_gh_dependencies( + mox_path, + installation_cleanup_dependencies, + installation_temp_path: Path, + installation_keep_gh_dependencies, +): + current_dir = Path.cwd() + try: + os.chdir(installation_temp_path) + result = subprocess.run( + [mox_path, "install"], check=True, capture_output=True, text=True + ) + finally: + os.chdir(current_dir) + + assert "Installing 2 GitHub packages..." in result.stderr + + assert installation_temp_path.joinpath(MOCCASIN_TOML).exists() + assert installation_temp_path.joinpath(LIB_GH_PATH).exists() + assert any(installation_temp_path.joinpath(LIB_GH_PATH).iterdir()) + + assert installation_temp_path.joinpath(LIB_PIP_PATH).exists() + assert not any(installation_temp_path.joinpath(LIB_PIP_PATH).iterdir()) + + +def test_run_install_all_dependencies( + mox_path, installation_cleanup_dependencies, installation_temp_path: Path +): + current_dir = Path.cwd() + try: + os.chdir(installation_temp_path) + result = subprocess.run( + [mox_path, "install"], check=True, capture_output=True, text=True + ) + finally: + os.chdir(current_dir) + + gh_dir_path = installation_temp_path.joinpath(LIB_GH_PATH) + pip_dir_path = installation_temp_path.joinpath(LIB_PIP_PATH) + + assert "Installing 2 pip packages..." in result.stderr + assert "Installing 1 GitHub packages..." in result.stderr + assert installation_temp_path.joinpath(MOCCASIN_TOML).exists() + assert gh_dir_path.exists() + assert any(gh_dir_path.iterdir()) + assert gh_dir_path.joinpath(VERSIONS_TOML).exists() + assert gh_dir_path.joinpath(PATRICK_PACKAGE_NAME).exists() + + assert pip_dir_path.exists() + assert any(pip_dir_path.iterdir()) + assert pip_dir_path.joinpath(PIP_PACKAGE_NAME).exists() + assert pip_dir_path.joinpath(MOCCASIN_LIB_NAME).exists() + + +def test_run_install_update_pip_and_gh( + mox_path, + installation_cleanup_dependencies, + installation_temp_path: Path, + installation_keep_full_dependencies, +): + current_dir = Path.cwd() + try: + os.chdir(installation_temp_path) + # Run install one time to get all dependencies + subprocess.run( + [mox_path, "install"], check=True, capture_output=True, text=True + ) + # Run install again to update dependencies + result = subprocess.run( + [mox_path, "install", "snekmate==0.0.5", "pcaversaccio/snekmate@0.0.5"], + check=True, + capture_output=True, + text=True, + ) + finally: + os.chdir(current_dir) + + gh_dir_path = installation_temp_path.joinpath(LIB_GH_PATH) + pip_dir_path = installation_temp_path.joinpath(LIB_PIP_PATH) + gh_message = f"Updated {GITHUB_PACKAGE_NAME}@{NEW_VERSION}" + pip_message = f"Updated package: {PIP_PACKAGE_NAME}=={NEW_VERSION}" + + assert gh_message in result.stderr + assert pip_message in result.stderr + assert "Installing 1 GitHub packages..." in result.stderr + assert "Installing 1 pip packages..." in result.stderr + assert installation_temp_path.joinpath(MOCCASIN_TOML).exists() + + assert gh_dir_path.joinpath(PATRICK_PACKAGE_NAME).exists() + assert gh_dir_path.joinpath(GITHUB_PACKAGE_NAME).exists() + + assert pip_dir_path.joinpath(PIP_PACKAGE_NAME).exists() + assert pip_dir_path.joinpath(MOCCASIN_LIB_NAME).exists() + + assert gh_dir_path.joinpath(VERSIONS_TOML).exists() + + +@pytest.mark.parametrize( + "dependencies_to_add, expected_dependencies, expected_gh_versions", + [ + # Default moccasin.toml install + ( + [], + ["snekmate", "moccasin", "PatrickAlphaC/test_repo"], + {"patrickalphac/test_repo": "0.1.1"}, + ), + # Change pip specification + ( + [f"{PIP_PACKAGE_NAME}>={VERSION}", f"{MOCCASIN_LIB_NAME}==0.3.6"], + [ + "PatrickAlphaC/test_repo", + f"{PIP_PACKAGE_NAME}>={VERSION}", + f"{MOCCASIN_LIB_NAME}==0.3.6", + ], + {"patrickalphac/test_repo": "0.1.1"}, + ), + # Change gh specification + ( + [f"{PATRICK_PACKAGE_NAME}@0.1.0"], + [ + f"{PIP_PACKAGE_NAME}>={VERSION}", + f"{MOCCASIN_LIB_NAME}==0.3.6", + "patrickalphac/test_repo@0.1.0", + ], + {"patrickalphac/test_repo": "0.1.0"}, + ), + ], +) +def test_run_multiple_install( + mox_path, + installation_temp_path: Path, + dependencies_to_add, + expected_dependencies, + expected_gh_versions, +): + """ + @dev Little warning about the way we write the + dependencies inside the moccasin.toml file. + + If `mox install`, then order will be like this: + - Pip packages first + - Github packages second + + If `mox install ` then we will keep the order of the + dependencies in the moccasin.toml file with the new package at the end. + Disregarding if the package is a pip or a github package. + """ + current_dir = Path.cwd() + try: + os.chdir(installation_temp_path) + base_command = [mox_path, "install"] + subprocess.run( + base_command + dependencies_to_add, + check=True, + capture_output=True, + text=True, + ) + finally: + os.chdir(current_dir) + + gh_dir_path = installation_temp_path.joinpath(LIB_GH_PATH) + pip_dir_path = installation_temp_path.joinpath(LIB_PIP_PATH) + + assert installation_temp_path.joinpath(MOCCASIN_TOML).exists() + assert gh_dir_path.joinpath(PATRICK_PACKAGE_NAME).exists() + assert pip_dir_path.joinpath(PIP_PACKAGE_NAME).exists() + assert pip_dir_path.joinpath(MOCCASIN_LIB_NAME).exists() + + # Verify config state if versions are expected + project_root: Path = Config.find_project_root(installation_temp_path) + config = Config(project_root) + assert config.dependencies == expected_dependencies + + # Verify versions file contents + github_versions = get_temp_versions_toml_gh(installation_temp_path) + assert github_versions == expected_gh_versions diff --git a/tests/cli/test_cli_run.py b/tests/cli/test_cli_run.py index 040f5d0..229684d 100644 --- a/tests/cli/test_cli_run.py +++ b/tests/cli/test_cli_run.py @@ -2,10 +2,25 @@ import subprocess from pathlib import Path +import pytest +import tomli_w +from packaging.requirements import Requirement + +from moccasin.config import Config from tests.constants import ( ANVIL1_KEYSTORE_NAME, ANVIL1_KEYSTORE_PASSWORD, ANVIL1_PRIVATE_KEY, + LIB_GH_PATH, + LIB_PIP_PATH, + MOCCASIN_LIB_NAME, + MOCCASIN_TOML, + PIP_PACKAGE_NAME, + VERSION, +) +from tests.utils.helpers import ( + get_temp_versions_toml_gh, + rewrite_temp_moccasin_toml_dependencies, ) @@ -24,7 +39,7 @@ def test_run_help(mox_path, complex_temp_path): assert "Moccasin CLI run" in result.stdout -def test_run_default(mox_path, complex_temp_path): +def test_run_default(mox_path, complex_temp_path, complex_cleanup_dependencies_folder): current_dir = Path.cwd() try: os.chdir(complex_temp_path) @@ -36,6 +51,89 @@ def test_run_default(mox_path, complex_temp_path): assert "Ending count: 1" in result.stdout +# @dev test adapted to the ordering of dependencies +@pytest.mark.parametrize( + "cli_args, rewrite_dependencies, expected_lib_path, expected_pip_deps, expected_gh_deps, expected_gh_versions", + [ + # --no-install should skip package installation + (["deploy", "--no-install"], [], False, ["snekmate==0.1.0"], [], None), + # Default behavior - installs dependencies + ( + ["deploy"], + [ + "PatrickAlphaC/test_repo", + f"{PIP_PACKAGE_NAME}>={VERSION}", + f"{MOCCASIN_LIB_NAME}==0.3.6", + ], + True, + [f"{PIP_PACKAGE_NAME}>={VERSION}", f"{MOCCASIN_LIB_NAME}==0.3.6"], + ["PatrickAlphaC/test_repo"], + {"patrickalphac/test_repo": "0.1.1"}, + ), + # Change compiled file + (["deploy"], [], True, ["snekmate==0.1.0"], [], None), + ], +) +def test_run_default_with_flags( + complex_temp_path, + complex_cleanup_out_folder, + complex_cleanup_dependencies_folder, + mox_path, + cli_args, + rewrite_dependencies, + expected_lib_path, + expected_pip_deps, + expected_gh_deps, + expected_gh_versions, +): + current_dir = Path.cwd() + old_moccasin_toml = rewrite_temp_moccasin_toml_dependencies( + complex_temp_path, rewrite_dependencies + ) + + try: + os.chdir(current_dir.joinpath(complex_temp_path)) + base_args = [mox_path, "run"] + result = subprocess.run( + base_args + cli_args, check=True, capture_output=True, text=True + ) + finally: + os.chdir(current_dir) + + assert complex_temp_path.joinpath(MOCCASIN_TOML).exists() + + gh_dir_path = complex_temp_path.joinpath(LIB_GH_PATH) + pip_dir_path = complex_temp_path.joinpath(LIB_PIP_PATH) + assert gh_dir_path.exists() == expected_lib_path + assert pip_dir_path.exists() == expected_lib_path + + for dep in expected_pip_deps: + pip_requirement = Requirement(dep) + assert pip_dir_path.joinpath(pip_requirement.name).exists() == expected_lib_path + if expected_gh_deps: + for dep in expected_gh_deps: + assert ( + gh_dir_path.joinpath(dep.lower().split("@")[0]).exists() + == expected_lib_path + ) + + # Verify config state if versions are expected + project_root: Path = Config.find_project_root(complex_temp_path) + config = Config(project_root) + assert config.dependencies == expected_pip_deps + expected_gh_deps + + # Verify gh versions file contents + if expected_gh_versions: + github_versions = get_temp_versions_toml_gh(complex_temp_path) + assert github_versions == expected_gh_versions + + assert "Ending count: 1" in result.stdout + + # Reset toml to the original for next test + with open(complex_temp_path.joinpath(MOCCASIN_TOML), "wb") as f: + tomli_w.dump(old_moccasin_toml, f) + + def test_multiple_manifest_returns_the_same_or_different(mox_path, complex_temp_path): current_dir = Path.cwd() os.chdir(complex_temp_path) diff --git a/tests/cli/test_cli_test.py b/tests/cli/test_cli_test.py index bcb9b2a..7a980d6 100644 --- a/tests/cli/test_cli_test.py +++ b/tests/cli/test_cli_test.py @@ -2,7 +2,24 @@ import subprocess from pathlib import Path -from tests.constants import COMPLEX_PROJECT_PATH +import pytest +import tomli_w +from packaging.requirements import Requirement + +from moccasin.config import Config +from tests.constants import ( + GITHUB_PACKAGE_NAME, + LIB_GH_PATH, + LIB_PIP_PATH, + MOCCASIN_TOML, + NEW_VERSION, + PIP_PACKAGE_NAME, + VERSION, +) +from tests.utils.helpers import ( + get_temp_versions_toml_gh, + rewrite_temp_moccasin_toml_dependencies, +) EXPECTED_HELP_TEXT = "Runs pytest" @@ -11,13 +28,19 @@ def test_test_help(mox_path): result = subprocess.run( [mox_path, "test", "-h"], check=True, capture_output=True, text=True ) - assert ( - EXPECTED_HELP_TEXT in result.stdout - ), "Help output does not contain expected text" + assert EXPECTED_HELP_TEXT in result.stdout, ( + "Help output does not contain expected text" + ) assert result.returncode == 0 -def test_basic(mox_path, complex_temp_path, anvil): +def test_basic( + mox_path, + complex_temp_path, + complex_cleanup_out_folder, + complex_cleanup_dependencies_folder, + anvil, +): current_dir = Path.cwd() try: os.chdir(current_dir.joinpath(complex_temp_path)) @@ -32,10 +55,94 @@ def test_basic(mox_path, complex_temp_path, anvil): assert "1 skipped" in result.stdout -def test_test_complex_project_has_no_warnings(complex_cleanup_out_folder, mox_path): +# @dev test adapted to the ordering of dependencies +@pytest.mark.parametrize( + "cli_args, rewrite_dependencies, expected_lib_path, expected_pip_deps, expected_gh_deps, expected_gh_versions", + [ + # --no-install should skip package installation + (["--no-install"], [], False, ["snekmate==0.1.0"], [], None), + # Default behavior - installs dependencies + ( + [], + [f"{GITHUB_PACKAGE_NAME}@{NEW_VERSION}", f"{PIP_PACKAGE_NAME}>={VERSION}"], + True, + [f"{PIP_PACKAGE_NAME}>={VERSION}"], + [f"{GITHUB_PACKAGE_NAME}@{NEW_VERSION}"], + {f"{GITHUB_PACKAGE_NAME}": NEW_VERSION}, + ), + # Change compiled file + ([], [], True, ["snekmate==0.1.0"], [], None), + ], +) +def test_test_basic_with_flags( + complex_temp_path, + complex_cleanup_out_folder, + complex_cleanup_dependencies_folder, + mox_path, + anvil, + cli_args, + rewrite_dependencies, + expected_lib_path, + expected_pip_deps, + expected_gh_deps, + expected_gh_versions, +): current_dir = Path.cwd() + old_moccasin_toml = rewrite_temp_moccasin_toml_dependencies( + complex_temp_path, rewrite_dependencies + ) + try: - os.chdir(current_dir.joinpath(COMPLEX_PROJECT_PATH)) + os.chdir(current_dir.joinpath(complex_temp_path)) + base_args = [mox_path, "test"] + result = subprocess.run(base_args + cli_args, capture_output=True, text=True) + finally: + os.chdir(current_dir) + + assert complex_temp_path.joinpath(MOCCASIN_TOML).exists() + + gh_dir_path = complex_temp_path.joinpath(LIB_GH_PATH) + pip_dir_path = complex_temp_path.joinpath(LIB_PIP_PATH) + assert gh_dir_path.exists() == expected_lib_path + assert pip_dir_path.exists() == expected_lib_path + + for dep in expected_pip_deps: + pip_requirement = Requirement(dep) + assert pip_dir_path.joinpath(pip_requirement.name).exists() == expected_lib_path + if expected_gh_deps: + for dep in expected_gh_deps: + assert ( + gh_dir_path.joinpath(dep.lower().split("@")[0]).exists() + == expected_lib_path + ) + + # Verify config state if versions are expected + project_root: Path = Config.find_project_root(complex_temp_path) + config = Config(project_root) + print(config.dependencies) + print(expected_pip_deps + expected_gh_deps) + assert config.dependencies == expected_pip_deps + expected_gh_deps + + # Verify gh versions file contents + if expected_gh_versions: + github_versions = get_temp_versions_toml_gh(complex_temp_path) + assert github_versions == expected_gh_versions + + # Check for the error message in the output + assert "8 passed" in result.stdout + assert "1 skipped" in result.stdout + + # Reset toml to the original for next test + with open(complex_temp_path.joinpath(MOCCASIN_TOML), "wb") as f: + tomli_w.dump(old_moccasin_toml, f) + + +def test_test_complex_project_has_no_warnings( + complex_cleanup_out_folder, complex_temp_path, mox_path +): + current_dir = Path.cwd() + try: + os.chdir(current_dir.joinpath(complex_temp_path)) result = subprocess.run( [mox_path, "test"], check=True, capture_output=True, text=True ) @@ -45,10 +152,12 @@ def test_test_complex_project_has_no_warnings(complex_cleanup_out_folder, mox_pa assert result.returncode == 0 -def test_test_complex_project_passes_pytest_flags(complex_cleanup_out_folder, mox_path): +def test_test_complex_project_passes_pytest_flags( + complex_cleanup_out_folder, complex_temp_path, mox_path +): current_dir = Path.cwd() try: - os.chdir(current_dir.joinpath(COMPLEX_PROJECT_PATH)) + os.chdir(current_dir.joinpath(complex_temp_path)) result = subprocess.run( [mox_path, "test", "-k", "test_increment_two"], check=True, @@ -62,10 +171,12 @@ def test_test_complex_project_passes_pytest_flags(complex_cleanup_out_folder, mo assert result.returncode == 0 -def test_test_coverage(complex_cleanup_out_folder, complex_cleanup_coverage, mox_path): +def test_test_coverage( + complex_cleanup_out_folder, complex_cleanup_coverage, complex_temp_path, mox_path +): current_dir = Path.cwd() try: - os.chdir(current_dir.joinpath(COMPLEX_PROJECT_PATH)) + os.chdir(current_dir.joinpath(complex_temp_path)) result = subprocess.run( [mox_path, "test", "--coverage"], check=True, capture_output=True, text=True ) @@ -73,13 +184,15 @@ def test_test_coverage(complex_cleanup_out_folder, complex_cleanup_coverage, mox os.chdir(current_dir) assert "coverage:" in result.stdout assert "Computation" not in result.stdout - assert current_dir.joinpath(COMPLEX_PROJECT_PATH).joinpath(".coverage").exists() + assert current_dir.joinpath(complex_temp_path).joinpath(".coverage").exists() -def test_test_gas(complex_cleanup_out_folder, complex_cleanup_coverage, mox_path): +def test_test_gas( + complex_cleanup_out_folder, complex_cleanup_coverage, complex_temp_path, mox_path +): current_dir = Path.cwd() try: - os.chdir(current_dir.joinpath(COMPLEX_PROJECT_PATH)) + os.chdir(current_dir.joinpath(complex_temp_path)) result = subprocess.run( [mox_path, "test", "--gas-profile"], check=True, diff --git a/tests/conftest.py b/tests/conftest.py index e5dd5c9..f2c00c0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,7 +19,11 @@ ANVIL_STORED_STATE_PATH, COMPLEX_PROJECT_PATH, INSTALL_PROJECT_PATH, + INSTALLATION_FULL_DEPENDENCIES_TOML, + INSTALLATION_NO_DEPENDENCIES_TOML, INSTALLATION_STARTING_TOML, + INSTALLATION_WITH_GH_TOML, + INSTALLATION_WITH_PIP_TOML, NO_CONFIG_PROJECT_PATH, PURGE_PROJECT_PATH, PURGE_STARTING_TOML, @@ -160,6 +164,29 @@ def installation_cleanup_dependencies(installation_temp_path): if os.path.exists(created_folder_path): shutil.rmtree(created_folder_path) +@pytest.fixture +def installation_remove_dependencies(installation_temp_path): + with open(installation_temp_path.joinpath("moccasin.toml"), "w") as f: + f.write(INSTALLATION_NO_DEPENDENCIES_TOML) + + +@pytest.fixture +def installation_keep_pip_dependencies(installation_temp_path): + with open(installation_temp_path.joinpath("moccasin.toml"), "w") as f: + f.write(INSTALLATION_WITH_PIP_TOML) + + +@pytest.fixture +def installation_keep_gh_dependencies(installation_temp_path): + with open(installation_temp_path.joinpath("moccasin.toml"), "w") as f: + f.write(INSTALLATION_WITH_GH_TOML) + + +@pytest.fixture +def installation_keep_full_dependencies(installation_temp_path): + with open(installation_temp_path.joinpath("moccasin.toml"), "w") as f: + f.write(INSTALLATION_FULL_DEPENDENCIES_TOML) + # ------------------------------------------------------------------ # PURGE PROJECT FIXTURES diff --git a/tests/constants.py b/tests/constants.py index 5e60995..2543496 100644 --- a/tests/constants.py +++ b/tests/constants.py @@ -29,7 +29,67 @@ # TEST TOML # ------------------------------------------------------------------ INSTALLATION_STARTING_TOML = """[project] -dependencies = ["snekmate", "moccasin"] +dependencies = [ + "snekmate", + "moccasin", + "PatrickAlphaC/test_repo", +] + +# PRESERVE COMMENTS + +[networks.sepolia] +url = "https://ethereum-sepolia-rpc.publicnode.com" +chain_id = 11155111 +save_to_db = false +""" + +INSTALLATION_NO_DEPENDENCIES_TOML = """[project] +dependencies = [] + +# PRESERVE COMMENTS + +[networks.sepolia] +url = "https://ethereum-sepolia-rpc.publicnode.com" +chain_id = 11155111 +save_to_db = false +""" + + +INSTALLATION_WITH_PIP_TOML = """[project] +dependencies = [ + "snekmate", + "moccasin", +] + +# PRESERVE COMMENTS + +[networks.sepolia] +url = "https://ethereum-sepolia-rpc.publicnode.com" +chain_id = 11155111 +save_to_db = false +""" + +INSTALLATION_WITH_GH_TOML = """[project] +dependencies = [ + "PatrickAlphaC/test_repo@0.1.1", + "pcaversaccio/snekmate", +] + +# PRESERVE COMMENTS + +[networks.sepolia] +url = "https://ethereum-sepolia-rpc.publicnode.com" +chain_id = 11155111 +save_to_db = false +""" + +INSTALLATION_FULL_DEPENDENCIES_TOML = """[project] +dependencies = [ + "snekmate", + "moccasin", + "PatrickAlphaC/test_repo@0.1.1", + "pcaversaccio/snekmate", +] # PRESERVE COMMENTS @@ -51,14 +111,21 @@ # ------------------------------------------------------------------ -# TEST GITHUB +# TEST LIB # ------------------------------------------------------------------ -PIP_PACKAGE_NAME = "snekmate" +# @dev latest version can be problematic to follow, so tests might fail later on +COMMENT_CONTENT = "PRESERVE COMMENTS" +NEW_VERSION = "0.0.5" ORG_NAME = "pcaversaccio" -GITHUB_PACKAGE_NAME = f"{ORG_NAME}/{PIP_PACKAGE_NAME}" +PIP_PACKAGE_NAME = "snekmate" VERSION = "0.1.0" -NEW_VERSION = "0.0.5" -COMMENT_CONTENT = "PRESERVE COMMENTS" +GITHUB_PACKAGE_NAME = f"{ORG_NAME}/{PIP_PACKAGE_NAME}" +LIB_GH_PATH = "lib/github" +LIB_PIP_PATH = "lib/pypi" +MOCCASIN_LIB_NAME = "moccasin" +MOCCASIN_TOML = "moccasin.toml" +MOCCASIN_VERSION = "0.3.8" PATRICK_ORG_NAME = "patrickalphac" PATRICK_REPO_NAME = "test_repo" PATRICK_PACKAGE_NAME = f"{PATRICK_ORG_NAME}/{PATRICK_REPO_NAME}" +VERSIONS_TOML = "versions.toml" \ No newline at end of file diff --git a/tests/integration/test_integration_explorer.py b/tests/integration/test_integration_explorer.py index 6fcb909..7c8f91a 100644 --- a/tests/integration/test_integration_explorer.py +++ b/tests/integration/test_integration_explorer.py @@ -3,7 +3,6 @@ from pathlib import Path from moccasin.commands.explorer import boa_get_abi_from_explorer -from tests.constants import COMPLEX_PROJECT_PATH CURVE_ADDRESS_ETH_MAINNET = "0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E" LINK_ADDRESS_OPT_MAINNET = "0x350a791Bfc2C21F9Ed5d10980Dad2e2638ffa7f6" @@ -47,9 +46,11 @@ def test_boa_get_abi_from_explorer_by_chain_id(complex_project_config): assert len(abi) == 26 -def test_get_abi_from_script_etherscan(mox_path, complex_project_config): +def test_get_abi_from_script_etherscan( + mox_path, complex_project_config, complex_temp_path +): current_dir = Path.cwd() - os.chdir(COMPLEX_PROJECT_PATH) + os.chdir(complex_temp_path) try: result = subprocess.run( [mox_path, "run", "get_usdc_balance", "--network", "mainnet_fork"], diff --git a/tests/live/test_live_verify.py b/tests/live/test_live_verify.py index 5728816..68299f9 100644 --- a/tests/live/test_live_verify.py +++ b/tests/live/test_live_verify.py @@ -4,14 +4,17 @@ import pytest -from tests.constants import COMPLEX_PROJECT_PATH - @pytest.mark.skip -def test_zksync_verify(mox_path): +def test_zksync_verify( + mox_path, + complex_temp_path, + complex_cleanup_dependencies_folder, + complex_cleanup_out_folder, +): current_dir = Path.cwd() try: - os.chdir(COMPLEX_PROJECT_PATH) + os.chdir(complex_temp_path) result = subprocess.run( [ mox_path, diff --git a/tests/utils/helpers.py b/tests/utils/helpers.py new file mode 100644 index 0000000..efb9db1 --- /dev/null +++ b/tests/utils/helpers.py @@ -0,0 +1,60 @@ +import tomllib +from pathlib import Path + +import tomli_w + +from tests.constants import LIB_GH_PATH, MOCCASIN_TOML, VERSIONS_TOML + + +def rewrite_temp_moccasin_toml_dependencies( + temp_path: Path, dependencies: list[str] | None +) -> dict: + """Rewrite the moccasin.toml file with new dependencies + + :param temp_path: Path to the temporary directory containing the moccasin.toml file. + :type temp_path: Path + :param dependencies: List of dependencies to add to the moccasin.toml file. + :type dependencies: list[str] + :return: Configuration data from the moccasin.toml file. + :rtype: dict + """ + if not temp_path.joinpath(MOCCASIN_TOML).exists(): + return {} + + # Read the moccasin.toml file + with open(temp_path.joinpath(MOCCASIN_TOML), "rb") as f: + old_moccasin_toml = tomllib.load(f) + + # Return if no dependencies are provided to keep the config + if dependencies is None or len(dependencies) == 0: + return old_moccasin_toml + + with open(temp_path.joinpath(MOCCASIN_TOML), "wb") as f: + # Update dependencies of moccasin.toml + # @dev add new dependencies and keep config + new_moccasin_toml = { + **old_moccasin_toml, + "project": {**old_moccasin_toml["project"], "dependencies": dependencies}, + } + tomli_w.dump(new_moccasin_toml, f) + + return old_moccasin_toml + + +def get_temp_versions_toml_gh(temp_path: Path) -> dict: + """Get the versions (github) from their lib/github toml file + + :param temp_path: Path to the temporary directory containing the moccasin.toml file. + :type temp_path: Path + :return: A dictionary containing the versions of the libraries. + :rtype: dict + """ + github_versions = {} + + # Github versions + if temp_path.joinpath(f"{LIB_GH_PATH}/{VERSIONS_TOML}").exists(): + with open(temp_path.joinpath(f"{LIB_GH_PATH}/{VERSIONS_TOML}"), "rb") as f: + versions = tomllib.load(f) + github_versions = {k.lower(): v for k, v in versions.items()} + + return github_versions