diff --git a/Makefile b/Makefile index c666b75c..2f208e20 100644 --- a/Makefile +++ b/Makefile @@ -22,13 +22,15 @@ lint: python -m tox -e lint -# Tests only, no lint, single python version. +# Offline tests only, no lint, single python version, skipping online tests. +# This is a partial but fast test. test: - python -m tox --skip-missing-interpreters false -e py312 + python -m tox --skip-missing-interpreters false -e py312 -- --offline -# Tests and lint, single python version. -# Run this before submitting code. +# Tests and lint, single python version, all tests including online.. +# This is a thorough but slow test and sufficient for testign before +# commiting changes run this before submitting code. check: python -m tox --skip-missing-interpreters false -e lint,py312 diff --git a/apio/apio_context.py b/apio/apio_context.py index 8547a73d..d648eebe 100644 --- a/apio/apio_context.py +++ b/apio/apio_context.py @@ -8,6 +8,7 @@ import sys import json +import re import platform from collections import OrderedDict import shutil @@ -91,14 +92,28 @@ def __init__( fg="yellow", ) + # -- A flag to indicate if the system env was already set in this + # -- apio session. Used to avoid multiple repeated settings that + # -- make the path longer and longer. + self.env_was_already_set = False + # -- Save the load project status. self._load_project = load_project # -- Maps the optional project_dir option to a path. self.project_dir: Path = util.get_project_dir(project_dir) + ApioContext._check_no_spaces_in_dir(self.project_dir, "project") + + # -- Determine apio home dir + self.home_dir: Path = ApioContext._get_home_dir() + ApioContext._check_no_spaces_in_dir(self.home_dir, "home") + + # -- Determine apio home dir + self.packages_dir: Path = ApioContext._get_packages_dir(self.home_dir) + ApioContext._check_no_spaces_in_dir(self.packages_dir, "packages") # -- Profile information, from ~/.apio/profile.json - self.profile = Profile() + self.profile = Profile(self.home_dir) # -- Read the platforms information. self.platforms = self._load_resource(PLATFORMS_JSON) @@ -110,7 +125,7 @@ def __init__( self.all_packages = self._load_resource(PACKAGES_JSON) # -- Expand in place the env templates in all_packages. - ApioContext._resolve_package_envs(self.all_packages) + ApioContext._resolve_package_envs(self.all_packages, self.packages_dir) # The subset of packages that are applicable to this platform. self.platform_packages = self._select_packages_for_platform( @@ -150,6 +165,23 @@ def __init__( self._project = Project(self.project_dir) self._project.read() + @staticmethod + def _check_no_spaces_in_dir(dir_path: Path, subject: str): + """Give the user an early error message if their apio setup dir + contains white space. See https://github.com/FPGAwars/apio/issues/474 + """ + # -- Match a single white space in the dir path. + if re.search("\\s", str(dir_path)): + # -- Here space found. This is a fatal error since we don't hand + # -- it well later in the process. + click.secho( + f"Error: The apio {subject} directory path contains white " + "space.", + fg="red", + ) + click.secho(f"'{str(dir_path)}'", fg="red") + sys.exit(1) + def check_project_loaded(self): """Assert that context was created with project loading..""" assert ( @@ -279,12 +311,13 @@ def _expand_env_template(template: str, package_path: Path) -> str: raise RuntimeError(f"Invalid env template: [{template}]") @staticmethod - def _resolve_package_envs(packages: Dict[str, Dict]) -> None: + def _resolve_package_envs( + packages: Dict[str, Dict], packages_dir: Path + ) -> None: """Resolve in place the path and var value templates in the given packages dictionary. For example, %p is replaced with the package's absolute path.""" - packages_dir = util.get_packages_dir() for _, package_config in packages.items(): # -- Get the package root dir. @@ -456,7 +489,7 @@ def get_package_dir(self, package_name: str) -> Path: """Returns the root path of a package with given name.""" package_folder = self.get_package_folder_name(package_name) - package_dir = util.get_packages_dir() / package_folder + package_dir = self.packages_dir / package_folder return package_dir @@ -575,3 +608,100 @@ def is_darwin(self) -> bool: def is_windows(self) -> bool: """Returns True iff platform_id indicates windows.""" return "windows" in self.platform_id + + @staticmethod + def _get_home_dir() -> Path: + """Get the absolute apio home dir. This is the apio folder where the + profle is located and the packages are installed (unless + APIO_PACKAGES_DIR is used). + The apio home dir can be overridden using the APIO_HOME_DIR environment + varible or in the /etc/apio.json file (in + Debian). If not set, the user_HOME/.apio folder is used by default: + Ej. Linux: /home/obijuan/.apio + If the folders does not exist, they are created + """ + + # -- Get the APIO_HOME_DIR env variable + # -- It returns None if it was not defined + apio_home_dir_env = env_options.get( + env_options.APIO_HOME_DIR, default=None + ) + + # -- Get the home dir. It is what the APIO_HOME_DIR env variable + # -- says, or the default folder if None + if apio_home_dir_env: + home_dir = Path(apio_home_dir_env) + else: + home_dir = Path.home() / ".apio" + + # -- Make it absolute + home_dir = home_dir.absolute() + + # -- Create the folder if it does not exist + try: + home_dir.mkdir(parents=True, exist_ok=True) + except PermissionError: + click.secho( + f"Error: no usable home directory {home_dir}", fg="red" + ) + sys.exit(1) + + # Return the home_dir as a Path + return home_dir + + @staticmethod + def _get_packages_dir(home_dir: Path) -> Path: + """Return the base directory of apio packages. + Packages are installed in the following folder: + * Default: $APIO_HOME_DIR/packages + * $APIO_PACKAGES_DIR: if the APIO_PACKAGES_DIR env variable is set + * INPUT: + - pkg_name: Package name (Ex. 'examples') + * OUTPUT: + - The package absolute folder (PosixPath) + (Ex. '/home/obijuan/.apio/packages) + The absolute path of the returned directory is guaranteed to have + the word packages in it. + """ + + # -- Get the APIO_PACKAGES_DIR env variable + # -- It returns None if it was not defined + packaged_dir_override = env_options.get(env_options.APIO_PACKAGES_DIR) + + # -- Handle override. + if packaged_dir_override: + # -- Verify that the override dir contains the word packages in its + # -- absolute path. This is a safety mechanism to prevent + # -- uninentional bulk deletions in unintended directories. We + # -- check it each time before we perform a package deletion. + path = Path(packaged_dir_override).absolute() + if "packages" not in str(path).lower(): + click.secho( + "Error: packages directory path does not contain the word " + f"packages: {str(path)}", + fg="red", + ) + click.secho( + "For safety reasons, if you use the environment variable " + "APIO_PACKAGE_DIR to override\n" + "the packages dir, the new directory must have the word " + "'packages' (case insensitive)\n" + "in its absolute path.", + fg="yellow", + ) + sys.exit(1) + + # -- Override is OK. Use it as the packages dir. + packages_dir = Path(packaged_dir_override) + + # -- Else, use the default value. + else: + # -- Ex '/home/obijuan/.apio/packages/tools-oss-cad-suite' + # -- Guaranteed to be absolute. + packages_dir = home_dir / "packages" + + # -- Sanity check. If this fails, this is a programming error. + assert "packages" in str(packages_dir).lower(), packages_dir + + # -- All done. + return packages_dir diff --git a/apio/cmd_util.py b/apio/cmd_util.py index 93868ded..b8bf593b 100644 --- a/apio/cmd_util.py +++ b/apio/cmd_util.py @@ -156,13 +156,21 @@ def check_exactly_one_param( """ # The the subset of ids of params that where used in the command. specified_param_ids = _specified_params(cmd_ctx, param_ids) - # If more 2 or more print an error and exit. - if len(specified_param_ids) != 1: + # If exactly one than we are good. + if len(specified_param_ids) == 1: + return + if len(specified_param_ids) < 1: + # -- User specified Less flags than required. canonical_aliases = _params_ids_to_aliases(cmd_ctx, param_ids) aliases_str = ", ".join(canonical_aliases) - fatal_usage_error( - cmd_ctx, f"One of [{aliases_str}] must be specified." + fatal_usage_error(cmd_ctx, f"Specify one of [{aliases_str}].") + else: + # -- User specified more flags than allowed. + canonical_aliases = _params_ids_to_aliases( + cmd_ctx, specified_param_ids ) + aliases_str = ", ".join(canonical_aliases) + fatal_usage_error(cmd_ctx, f"Specify only one of [{aliases_str}].") def check_at_least_one_param( diff --git a/apio/commands/drivers.py b/apio/commands/drivers.py index b1034edb..39a2328a 100644 --- a/apio/commands/drivers.py +++ b/apio/commands/drivers.py @@ -90,8 +90,8 @@ def cli( ): """Implements the drivers command.""" - # Make sure these params are exclusive. - cmd_util.check_at_most_one_param( + # User should select exactly on of these operations. + cmd_util.check_exactly_one_param( cmd_ctx, nameof(ftdi_install, ftdi_uninstall, serial_install, serial_uninstall), ) diff --git a/apio/commands/examples.py b/apio/commands/examples.py index c091f89c..fb95d53d 100644 --- a/apio/commands/examples.py +++ b/apio/commands/examples.py @@ -90,8 +90,6 @@ def cli( """Manage verilog examples.\n Install with `apio packages --install examples`""" - cmd_ctx.get_help() - # Make sure these params are exclusive. cmd_util.check_exactly_one_param( cmd_ctx, nameof(list_, fetch_dir, fetch_files) diff --git a/apio/commands/packages.py b/apio/commands/packages.py index de7b6d21..ec72b1b6 100644 --- a/apio/commands/packages.py +++ b/apio/commands/packages.py @@ -138,6 +138,7 @@ def _list(apio_ctx: ApioContext, verbose: bool) -> int: apio packages --install oss-cad-suite # Install a specific package. apio packages --install examples@0.0.32 # Install a specific version. apio packages --uninstall # Uninstall all packages. + apio packages --uninstall --sayyes # Same but does not ask yes/no. apio packages --uninstall oss-cad-suite # Uninstall only given package(s). apio packages --fix # Fix package errors. @@ -223,6 +224,10 @@ def cli( cmd_util.check_at_most_one_param(cmd_ctx, nameof(list_, packages)) cmd_util.check_at_most_one_param(cmd_ctx, nameof(fix, packages)) + cmd_util.check_at_most_one_param(cmd_ctx, nameof(sayyes, list_)) + cmd_util.check_at_most_one_param(cmd_ctx, nameof(sayyes, install)) + cmd_util.check_at_most_one_param(cmd_ctx, nameof(sayyes, fix)) + # -- Create the apio context. apio_ctx = ApioContext(project_dir=project_dir, load_project=False) diff --git a/apio/commands/system.py b/apio/commands/system.py index a38971b7..b3afb141 100644 --- a/apio/commands/system.py +++ b/apio/commands/system.py @@ -156,11 +156,11 @@ def cli( # -- Print apio home directory. click.secho("Apio home ", nl=False) - click.secho(util.get_home_dir(), fg="cyan") + click.secho(apio_ctx.home_dir, fg="cyan") # -- Print apio home directory. click.secho("Apio packages ", nl=False) - click.secho(util.get_packages_dir(), fg="cyan") + click.secho(apio_ctx.packages_dir, fg="cyan") cmd_ctx.exit(0) diff --git a/apio/managers/installer.py b/apio/managers/installer.py index 3dcda07f..2310c606 100644 --- a/apio/managers/installer.py +++ b/apio/managers/installer.py @@ -296,14 +296,13 @@ def install_package( print(f"Download URL: {download_url}") # -- Prepare the packages directory. - packages_dir = util.get_packages_dir() - packages_dir.mkdir(exist_ok=True) + apio_ctx.packages_dir.mkdir(exist_ok=True) # -- Prepare the package directory. package_dir = apio_ctx.get_package_dir(package_name) # -- Downlod the package file from the remote server. - local_file = _download_package_file(download_url, packages_dir) + local_file = _download_package_file(download_url, apio_ctx.packages_dir) if verbose: print(f"Local file: {local_file}") @@ -319,14 +318,14 @@ def install_package( # -- Case 1: The package include a top level wrapper directory. # # -- Unpack the package one level up, in the packages directory. - _unpack_package_file(local_file, packages_dir) + _unpack_package_file(local_file, apio_ctx.packages_dir) # -- The uncompressed name may contain a %V placeholder, Replace it # -- with target version. uncompressed_name = uncompressed_name.replace("%V", target_version) # -- Construct the local path of the wrapper dir. - wrapper_dir = packages_dir / uncompressed_name + wrapper_dir = apio_ctx.packages_dir / uncompressed_name # -- Rename the wrapper dir to the package dir. if verbose: @@ -427,9 +426,9 @@ def fix_packages( for dir_name in scan.orphan_dir_names: if verbose: print(f"Deleting unknown dir '{dir_name}'") - # -- Sanity check. Since get_packages_dir() guarranted to include + # -- Sanity check. Since apio_ctx.packages_dir is guarranted to include # -- the word packages, this can fail only due to programming error. - dir_path = util.get_packages_dir() / dir_name + dir_path = apio_ctx.packages_dir / dir_name assert "packages" in str(dir_path).lower(), dir_path # -- Delete. shutil.rmtree(dir_path) @@ -437,9 +436,10 @@ def fix_packages( for file_name in scan.orphan_file_names: if verbose: print(f"Deleting unknown file '{file_name}'") - # -- Sanity check. Since get_packages_dir() guarranted to include - # -- the word packages, this can fail only due to programming error. - file_path = util.get_packages_dir() / file_name + # -- Sanity check. Since apio_ctx.packages_dir is guarranted to + # -- include the word packages, this can fail only due to programming + # -- error. + file_path = apio_ctx.packages_dir / file_name assert "packages" in str(file_path).lower(), dir_path # -- Delete. file_path.unlink() diff --git a/apio/managers/old_installer.py b/apio/managers/old_installer.py index 96060c38..69c25018 100644 --- a/apio/managers/old_installer.py +++ b/apio/managers/old_installer.py @@ -14,6 +14,7 @@ import requests from apio import util +from apio.apio_context import ApioContext from apio.managers.downloader import FileDownloader from apio.managers.unpacker import FileUnpacker @@ -37,7 +38,7 @@ class Modifiers: def __init__( self, package: str, - apio_ctx=None, + apio_ctx: ApioContext = None, modifiers=Modifiers(force=False, checkversion=True, verbose=False), ): """Class initialization. Parameters: @@ -94,7 +95,7 @@ def __init__( # --(It is defined in the resources/packages.json file) if self.package in self.apio_ctx.platform_packages: # -- Store the package dir - self.packages_dir = util.get_home_dir() / dirname + self.packages_dir = apio_ctx.packages_dir / dirname # Get the metadata of the given package package_info = self.apio_ctx.platform_packages[self.package] @@ -141,7 +142,7 @@ def __init__( self.package in self.apio_ctx.profile.packages and modifiers.checkversion is False ): - self.packages_dir = util.get_home_dir() / dirname + self.packages_dir = apio_ctx.home_dir / dirname self.package_folder_name = "toolchain-" + package diff --git a/apio/managers/unpacker.py b/apio/managers/unpacker.py index c87b31b5..00c743cd 100644 --- a/apio/managers/unpacker.py +++ b/apio/managers/unpacker.py @@ -21,8 +21,9 @@ class ArchiveBase: """DOC: TODO""" - def __init__(self, arhfileobj): + def __init__(self, arhfileobj, is_tar_file: bool): self._afo = arhfileobj + self._is_tar_file = is_tar_file def get_items(self): # pragma: no cover """DOC: TODO""" @@ -34,7 +35,13 @@ def extract_item(self, item, dest_dir): if hasattr(item, "filename") and item.filename.endswith(".gitignore"): return - self._afo.extract(item, dest_dir) + if self._is_tar_file and util.get_python_ver_tuple() >= (3, 12, 0): + # -- Special case for avoiding the tar deprecation warning. Search + # -- 'extraction_filter' in the page + # -- https://docs.python.org/3/library/tarfile.html + self._afo.extract(item, dest_dir, filter="fully_trusted") + else: + self._afo.extract(item, dest_dir) self.after_extract(item, dest_dir) def after_extract(self, item, dest_dir): @@ -48,7 +55,7 @@ def __init__(self, archpath): # R1732: Consider using 'with' for resource-allocating operations # (consider-using-with) # pylint: disable=R1732 - ArchiveBase.__init__(self, tarfile_open(archpath)) + ArchiveBase.__init__(self, tarfile_open(archpath), is_tar_file=True) def get_items(self): return self._afo.getmembers() @@ -61,7 +68,7 @@ def __init__(self, archpath): # R1732: Consider using 'with' for resource-allocating operations # (consider-using-with) # pylint: disable=R1732 - ArchiveBase.__init__(self, ZipFile(archpath)) + ArchiveBase.__init__(self, ZipFile(archpath), is_tar_file=False) @staticmethod def preserve_permissions(item, dest_dir): diff --git a/apio/pkg_util.py b/apio/pkg_util.py index e7c4b5c7..a8757e1b 100644 --- a/apio/pkg_util.py +++ b/apio/pkg_util.py @@ -117,8 +117,6 @@ def set_env_for_packages(apio_ctx: ApioContext, verbose: bool = False) -> None: The function sets the environment only on first call and in latter calls skips the operation silently. """ - # pylint: disable=global-statement - global __ENV_ALREADY_SET_FLAG # -- Collect the env mutations for all packages. mutations = _get_env_mutations_for_packages(apio_ctx) @@ -126,13 +124,14 @@ def set_env_for_packages(apio_ctx: ApioContext, verbose: bool = False) -> None: if verbose: _dump_env_mutations(apio_ctx, mutations) - # -- If this is the first call, apply the mutations. These mutations are - # -- temporary for the lifetime of this process and does not affect the - # -- user's shell environment. The mutations are also inheritated by - # -- child processes such as the scons processes. - if not __ENV_ALREADY_SET_FLAG: + # -- If this is the first call in this apio invocation, apply the + # -- mutations. These mutations are temporary for the lifetime of this + # -- process and does not affect the user's shell environment. + # -- The mutations are also inheritated by child processes such as the + # -- scons processes. + if not apio_ctx.env_was_already_set: _apply_env_mutations(mutations) - __ENV_ALREADY_SET_FLAG = True + apio_ctx.env_was_already_set = True if not verbose: click.secho("Setting the envinronment.") @@ -331,7 +330,7 @@ def scan_packages(apio_ctx: ApioContext) -> PackageScanResults: result.orphan_package_ids.append(package_id) # -- Scan the packages directory and identify orphan dirs and files. - for path in util.get_packages_dir().glob("*"): + for path in apio_ctx.packages_dir.glob("*"): base_name = os.path.basename(path) if path.is_dir(): if base_name not in platform_folder_names: diff --git a/apio/profile.py b/apio/profile.py index d45e537e..e72b152b 100644 --- a/apio/profile.py +++ b/apio/profile.py @@ -9,7 +9,6 @@ from pathlib import Path import click import semantic_version -from apio import util class Profile: @@ -17,7 +16,7 @@ class Profile: ex. ~/.apio/profile.json """ - def __init__(self): + def __init__(self, home_dir: Path): # ---- Set the default parameters # Apio settings @@ -28,7 +27,7 @@ def __init__(self): # -- Get the profile path # -- Ex. '/home/obijuan/.apio' - self._profile_path = util.get_home_dir() / "profile.json" + self._profile_path = home_dir / "profile.json" # -- Read the profile from file self.load() diff --git a/apio/resources/packages.json b/apio/resources/packages.json index 83f2148a..58afc98a 100644 --- a/apio/resources/packages.json +++ b/apio/resources/packages.json @@ -2,7 +2,7 @@ "examples": { "repository": { "name": "apio-examples", - "organization": "FPGAwars" + "organization": "zapta" }, "release": { "tag_name": "%V", @@ -10,7 +10,7 @@ "uncompressed_name": "apio-examples-%V", "folder_name": "examples", "extension": "zip", - "url_version": "https://github.com/FPGAwars/apio-examples/raw/master/VERSION" + "url_version": "https://github.com/zapta/apio-examples/raw/master/VERSION" }, "description": "Verilog examples", "env": {} @@ -112,4 +112,4 @@ ] } } -} \ No newline at end of file +} diff --git a/apio/scons/ecp5/SConstruct b/apio/scons/ecp5/SConstruct index b7633c89..6ac47705 100644 --- a/apio/scons/ecp5/SConstruct +++ b/apio/scons/ecp5/SConstruct @@ -48,6 +48,7 @@ from SCons.Script import ( ) from apio.scons.scons_util import ( TARGET, + BUILD_DIR, SConstructId, is_testbench, basename, @@ -168,9 +169,10 @@ env.Append(BUILDERS={"PnR": pnr_builder}) # -- Builder (icepack, bitstream generator). # -- hardware.config -> hardware.bit. bitstream_builder = Builder( - action="ecppack --compress --db {0} {1} $SOURCE hardware.bit".format( + action="ecppack --compress --db {0} {1} $SOURCE {2}/hardware.bit".format( DATABASE_PATH, "" if not FPGA_IDCODE else f"--idcode {FPGA_IDCODE}", + BUILD_DIR, ), suffix=".bit", src_suffix=".config", diff --git a/apio/scons/scons_util.py b/apio/scons/scons_util.py index e515b97c..2700434e 100644 --- a/apio/scons/scons_util.py +++ b/apio/scons/scons_util.py @@ -773,7 +773,12 @@ def set_up_cleanup(env: SConsEnvironment) -> None: # -- Get the list of all files to clean. Scons adds to the list non # -- existing files from other targets it encountered. - files_to_clean = env.Glob(f"{BUILD_DIR_SEP}*") + env.Glob("zadig.ini") + files_to_clean = ( + env.Glob(f"{BUILD_DIR_SEP}*") + + env.Glob("zadig.ini") + + env.Glob(".sconsign.dblite") + + env.Glob("_build") + ) # -- TODO: Remove the cleanup of legacy files after releasing the first # -- release with the _build directory. @@ -792,6 +797,7 @@ def set_up_cleanup(env: SConsEnvironment) -> None: "Deleting also left-over files from previous release.", fg="yellow", ) + files_to_clean.extend(legacy_files_to_clean) # -- Create a dummy target. I diff --git a/apio/util.py b/apio/util.py index 2791c028..2222d455 100644 --- a/apio/util.py +++ b/apio/util.py @@ -15,14 +15,13 @@ import shutil from enum import Enum from dataclasses import dataclass -from typing import Optional, Any +from typing import Optional, Any, Tuple import subprocess from threading import Thread from pathlib import Path import click from serial.tools.list_ports import comports import requests -from apio import env_options # ---------------------------------------- # -- Constants @@ -151,101 +150,6 @@ def get_path_in_apio_package(subpath: str) -> Path: return path -def get_home_dir() -> Path: - """Get the absolute apio home dir. This is the apio folder where the - profle is located and the packages are installed (unless APIO_PACKAGES_DIR - is used). - The apio home dir can be overridden using the APIO_HOME_DIR environment - varible or in the /etc/apio.json file (in - Debian). If not set, the user_HOME/.apio folder is used by default: - Ej. Linux: /home/obijuan/.apio - If the folders does not exist, they are created - """ - - # -- Get the APIO_HOME_DIR env variable - # -- It returns None if it was not defined - apio_home_dir_env = env_options.get( - env_options.APIO_HOME_DIR, default=None - ) - - # -- Get the home dir. It is what the APIO_HOME_DIR env variable - # -- says, or the default folder if None - if apio_home_dir_env: - home_dir = Path(apio_home_dir_env) - else: - home_dir = Path.home() / ".apio" - - # -- Make it absolute - home_dir = home_dir.absolute() - - # -- Create the folder if it does not exist - try: - home_dir.mkdir(parents=True, exist_ok=True) - except PermissionError: - click.secho(f"Error: no usable home directory {home_dir}", fg="red") - sys.exit(1) - - # Return the home_dir as a Path - return home_dir - - -def get_packages_dir() -> Path: - """Return the base directory of apio packages. - Packages are installed in the following folder: - * Default: $APIO_HOME_DIR/packages - * $APIO_PACKAGES_DIR: if the APIO_PACKAGES_DIR env variable is set - * INPUT: - - pkg_name: Package name (Ex. 'examples') - * OUTPUT: - - The package absolute folder (PosixPath) - (Ex. '/home/obijuan/.apio/packages) - The absolute path of the returned directory is guaranteed to have - the word packages in it. - """ - - # -- Get the APIO_PACKAGES_DIR env variable - # -- It returns None if it was not defined - packaged_dir_override = env_options.get(env_options.APIO_PACKAGES_DIR) - - # -- Handle override. - if packaged_dir_override: - # -- Verify that the override dir contains the word packages in its - # -- absolute path. This is a safety mechanism to prevent uninentional - # -- bulk deletions in unintended directories. We check it each time - # -- before we perform a package deletion. - path = Path(packaged_dir_override).absolute() - if "packages" not in str(path).lower(): - click.secho( - "Error: packages directory path does not contain the word " - f"packages: {str(path)}", - fg="red", - ) - click.secho( - "For safety reasons, if you use the environment variable " - "APIO_PACKAGE_DIR to override\n" - "the packages dir, the new directory must have the word " - "'packages' (case insensitive)\n" - "in its absolute path.", - fg="yellow", - ) - sys.exit(1) - - # -- Override is OK. Use it as the packages dir. - packages_dir = Path(packaged_dir_override) - - # -- Else, use the default value. - else: - # -- Ex '/home/obijuan/.apio/packages/tools-oss-cad-suite' - # -- Guaranteed to be absolute. - packages_dir = get_home_dir() / "packages" - - # -- Sanity check. If this fails, this is a programming error. - assert "packages" in str(packages_dir).lower(), packages_dir - - # -- All done. - return packages_dir - - def call(cmd): """Execute the given command.""" @@ -577,6 +481,11 @@ def get_python_version() -> str: return f"{sys.version_info[0]}.{sys.version_info[1]}" +def get_python_ver_tuple() -> Tuple[int, int, int]: + """Return a tuple with the python version. e.g. (3, 12, 1).""" + return sys.version_info[:3] + + def safe_click(text, *args, **kwargs): """Prints text to the console handling potential Unicode errors, forwarding any additional arguments to click.echo. This permits diff --git a/test-boards/Alhambra-II/test_leds.py b/test-boards/Alhambra-II/test_leds.py index e4d54187..a253a29a 100644 --- a/test-boards/Alhambra-II/test_leds.py +++ b/test-boards/Alhambra-II/test_leds.py @@ -10,16 +10,16 @@ from click.testing import CliRunner # -- apio build entry point -from apio.commands.build import cli as cmd_build +from apio.commands.build import cli as apio_build # -- apio clean entry point -from apio.commands.clean import cli as cmd_clean +from apio.commands.clean import cli as apio_clean # -- apio verify entry point -from apio.commands.verify import cli as cmd_verify +from apio.commands.verify import cli as apio_verify # -- apio time entry point -from apio.commands.upload import cli as cmd_upload +from apio.commands.upload import cli as apio_upload # ------------------------------------------- @@ -42,7 +42,7 @@ def test_ledon_clean(): # ---------------------------- # -- Execute "apio clean" # ---------------------------- - result = CliRunner().invoke(cmd_clean) + result = CliRunner().invoke(apio_clean) # -- It should return an exit code of 0: success assert result.exit_code == 0, result.output @@ -55,7 +55,7 @@ def test_ledon_build(): # ---------------------------- # -- Execute "apio build" # ---------------------------- - result = CliRunner().invoke(cmd_build) + result = CliRunner().invoke(apio_build) # -- It should return an exit code of 0: success assert result.exit_code == 0, result.output @@ -71,7 +71,7 @@ def test_ledon_verify(): # ---------------------------- # -- Execute "apio verify" # ---------------------------- - result = CliRunner().invoke(cmd_verify) + result = CliRunner().invoke(apio_verify) # -- It should return an exit code of 0: success assert result.exit_code == 0, result.output @@ -85,7 +85,7 @@ def test_ledon_upload(): # ---------------------------- # -- Execute "apio upload" # ---------------------------- - result = CliRunner().invoke(cmd_upload) + result = CliRunner().invoke(apio_upload) # -- It should return an exit code of 0: success assert result.exit_code == 0, result.output diff --git a/test/commands/test_boards.py b/test/commands/test_boards.py index 4bc9d75c..21015543 100644 --- a/test/commands/test_boards.py +++ b/test/commands/test_boards.py @@ -2,8 +2,11 @@ Test for the "apio boards" command """ +from test.conftest import ApioRunner + # -- apio boards entry point -from apio.commands.boards import cli as cmd_boards +from apio.commands.boards import cli as apio_boards + CUSTOM_BOARDS = """ { @@ -25,17 +28,15 @@ """ -def test_list_ok(click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok): +def test_list_ok(apio_runner: ApioRunner): """Test normal board listing with the apio's boards.json.""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): - # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() - # -- Execute "apio boards" - result = click_cmd_runner.invoke(cmd_boards) - assert_apio_cmd_ok(result) + result = apio_runner.invoke(apio_boards) + apio_runner.assert_ok(result) # -- Note: pytest sees the piped version of the command's output. # -- Run 'apio build' | cat' to reproduce it. assert "Loading custom 'boards.json'" not in result.output @@ -44,23 +45,21 @@ def test_list_ok(click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok): assert "Total of 1 board" not in result.output -def test_custom_board( - click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok -): +def test_custom_board(apio_runner: ApioRunner): """Test boards listing with a custom boards.json file.""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Write a custom boards.json file in the project's directory. with open("boards.json", "w", encoding="utf-8") as f: f.write(CUSTOM_BOARDS) # -- Execute "apio boards" - result = click_cmd_runner.invoke(cmd_boards) - assert_apio_cmd_ok(result) + result = apio_runner.invoke(apio_boards) + apio_runner.assert_ok(result) # -- Note: pytest sees the piped version of the command's output. # -- Run 'apio build' | cat' to reproduce it. assert "Loading custom 'boards.json'" in result.output diff --git a/test/commands/test_build.py b/test/commands/test_build.py index 2ef5f8fc..eb82d003 100644 --- a/test/commands/test_build.py +++ b/test/commands/test_build.py @@ -2,56 +2,56 @@ Test for the "apio build" command """ +from test.conftest import ApioRunner + # -- apio build entry point -from apio.commands.build import cli as cmd_build +from apio.commands.build import cli as apio_build # pylint: disable=too-many-statements -def test_errors_without_apio_ini_1(click_cmd_runner, setup_apio_test_env): +def test_errors_without_apio_ini_1(apio_runner: ApioRunner): """Test: Various errors 1/2. All tests are without apio.ini and without apio packages installed.""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio build" - result = click_cmd_runner.invoke(cmd_build) + result = apio_runner.invoke(apio_build) assert result.exit_code != 0, result.output assert "Info: Project has no apio.ini file" in result.output assert "Error: insufficient arguments: missing board" in result.output assert "Error: Missing FPGA" in result.output # apio build --board icestick - result = click_cmd_runner.invoke(cmd_build, ["--board", "icestick"]) + result = apio_runner.invoke(apio_build, ["--board", "icestick"]) assert result.exit_code == 1, result.output assert "apio packages --install --force oss-cad-suite" in result.output # apio build --fpga iCE40-HX1K-VQ100 - result = click_cmd_runner.invoke( - cmd_build, ["--fpga", "iCE40-HX1K-VQ100"] - ) + result = apio_runner.invoke(apio_build, ["--fpga", "iCE40-HX1K-VQ100"]) assert result.exit_code == 1, result.output assert "apio packages --install --force oss-cad-suite" in result.output # apio build --type lp --size 8k --pack cm225:4k - result = click_cmd_runner.invoke( - cmd_build, ["--type", "lp", "--size", "8k", "--pack", "cm225:4k"] + result = apio_runner.invoke( + apio_build, ["--type", "lp", "--size", "8k", "--pack", "cm225:4k"] ) assert result.exit_code == 1, result.output assert "Error: insufficient arguments" in result.output # apio build --board icezum --size 1k - result = click_cmd_runner.invoke( - cmd_build, ["--board", "icezum", "--size", "1k"] + result = apio_runner.invoke( + apio_build, ["--board", "icezum", "--size", "1k"] ) assert result.exit_code != 0, result.output assert "apio packages --install --force oss-cad-suite" in result.output # apio build --board icezum --fpga iCE40-HX1K-TQ144 --type hx - result = click_cmd_runner.invoke( - cmd_build, + result = apio_runner.invoke( + apio_build, [ "--board", "icezum", @@ -65,30 +65,30 @@ def test_errors_without_apio_ini_1(click_cmd_runner, setup_apio_test_env): assert "apio packages --install --force oss-cad-suite" in result.output # apio build --board icezum --pack tq144 - result = click_cmd_runner.invoke( - cmd_build, ["--board", "icezum", "--pack", "tq144"] + result = apio_runner.invoke( + apio_build, ["--board", "icezum", "--pack", "tq144"] ) assert result.exit_code != 0, result.output assert "apio packages --install --force oss-cad-suite" in result.output # apio build --fpga iCE40-HX1K-TQ144 --pack tq144 --size 1k - result = click_cmd_runner.invoke( - cmd_build, + result = apio_runner.invoke( + apio_build, ["--fpga", "iCE40-HX1K-TQ144", "--pack", "tq144", "--size", "1k"], ) assert result.exit_code != 0, result.output assert "apio packages --install --force oss-cad-suite" in result.output # apio build --fpga iCE40-HX1K-TQ144 --type hx - result = click_cmd_runner.invoke( - cmd_build, ["--fpga", "iCE40-HX1K-TQ144", "--type", "hx"] + result = apio_runner.invoke( + apio_build, ["--fpga", "iCE40-HX1K-TQ144", "--type", "hx"] ) assert result.exit_code != 0, result.output assert "apio packages --install --force oss-cad-suite" in result.output # apio build --board icezum --size 8k - result = click_cmd_runner.invoke( - cmd_build, ["--board", "icezum", "--size", "8k"] + result = apio_runner.invoke( + apio_build, ["--board", "icezum", "--size", "8k"] ) assert result.exit_code != 0, result.output assert ( @@ -97,8 +97,8 @@ def test_errors_without_apio_ini_1(click_cmd_runner, setup_apio_test_env): ) # apio build --board icezum --fpga iCE40-HX1K-TQ144 --type lp - result = click_cmd_runner.invoke( - cmd_build, + result = apio_runner.invoke( + apio_build, [ "--board", "icezum", @@ -115,8 +115,8 @@ def test_errors_without_apio_ini_1(click_cmd_runner, setup_apio_test_env): ) # apio build --board icezum --fpga iCE40-HX1K-VQ100 - result = click_cmd_runner.invoke( - cmd_build, ["--board", "icezum", "--fpga", "iCE40-HX1K-VQ100"] + result = apio_runner.invoke( + apio_build, ["--board", "icezum", "--fpga", "iCE40-HX1K-VQ100"] ) assert result.exit_code != 0, result.output assert ( @@ -125,8 +125,8 @@ def test_errors_without_apio_ini_1(click_cmd_runner, setup_apio_test_env): ) # apio build --fpga iCE40-HX1K-TQ144 --type lp --size 8k - result = click_cmd_runner.invoke( - cmd_build, + result = apio_runner.invoke( + apio_build, ["--fpga", "iCE40-HX1K-TQ144", "--type", "lp", "--size", "8k"], ) assert result.exit_code != 0, result.output @@ -136,8 +136,8 @@ def test_errors_without_apio_ini_1(click_cmd_runner, setup_apio_test_env): ) # apio build --fpga iCE40-HX1K-TQ144 --pack vq100 - result = click_cmd_runner.invoke( - cmd_build, ["--fpga", "iCE40-HX1K-TQ144", "--pack", "vq100"] + result = apio_runner.invoke( + apio_build, ["--fpga", "iCE40-HX1K-TQ144", "--pack", "vq100"] ) assert result.exit_code != 0, result.output assert ( @@ -146,8 +146,8 @@ def test_errors_without_apio_ini_1(click_cmd_runner, setup_apio_test_env): ) # apio build --board icezum --pack vq100 - result = click_cmd_runner.invoke( - cmd_build, ["--board", "icezum", "--pack", "vq100"] + result = apio_runner.invoke( + apio_build, ["--board", "icezum", "--pack", "vq100"] ) assert result.exit_code != 0, result.output assert ( @@ -156,58 +156,58 @@ def test_errors_without_apio_ini_1(click_cmd_runner, setup_apio_test_env): ) # apio build --size 8k - result = click_cmd_runner.invoke(cmd_build, ["--size", "8k"]) + result = apio_runner.invoke(apio_build, ["--size", "8k"]) assert result.exit_code != 0, result.output assert "Error: insufficient arguments" in result.output -def test_errors_without_apio_ini_2(click_cmd_runner, setup_apio_test_env): +def test_errors_without_apio_ini_2(apio_runner: ApioRunner): """Test: Various errors 2/2. All tests are without apio.ini and without apio packages installed.""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # apio build --type lp - result = click_cmd_runner.invoke(cmd_build, ["--type", "lp"]) + result = apio_runner.invoke(apio_build, ["--type", "lp"]) assert result.exit_code != 0, result.output assert "Error: insufficient arguments" in result.output # apio build --type lp --size 8k - result = click_cmd_runner.invoke( - cmd_build, ["--type", "lp", "--size", "8k"] + result = apio_runner.invoke( + apio_build, ["--type", "lp", "--size", "8k"] ) assert result.exit_code != 0, result.output assert "Error: insufficient arguments" in result.output # apio build --board icefake - result = click_cmd_runner.invoke(cmd_build, ["--board", "icefake"]) + result = apio_runner.invoke(apio_build, ["--board", "icefake"]) assert result.exit_code != 0, result.output assert "Error: unknown board: icefake" in result.output # apio build --board icefake --fpga iCE40-HX1K-TQ144 - result = click_cmd_runner.invoke( - cmd_build, ["--board", "icefake", "--fpga", "iCE40-HX1K-TQ144"] + result = apio_runner.invoke( + apio_build, ["--board", "icefake", "--fpga", "iCE40-HX1K-TQ144"] ) assert result.exit_code != 0, result.output assert "Error: unknown board: icefake" in result.output # apio build --fpga iCE40-FAKE - result = click_cmd_runner.invoke(cmd_build, ["--fpga", "iCE40-FAKE"]) + result = apio_runner.invoke(apio_build, ["--fpga", "iCE40-FAKE"]) assert result.exit_code != 0, result.output assert "Error: unknown FPGA: iCE40-FAKE" in result.output # apio build --fpga iCE40-FAKE --size 8k - result = click_cmd_runner.invoke( - cmd_build, ["--fpga", "iCE40-FAKE", "--size", "8k"] + result = apio_runner.invoke( + apio_build, ["--fpga", "iCE40-FAKE", "--size", "8k"] ) assert result.exit_code != 0, result.output assert "Error: unknown FPGA: iCE40-FAKE" in result.output # apio build --board icezum --fpga iCE40-FAKE - result = click_cmd_runner.invoke( - cmd_build, ["--board", "icezum", "--fpga", "iCE40-FAKE"] + result = apio_runner.invoke( + apio_build, ["--board", "icezum", "--fpga", "iCE40-FAKE"] ) assert result.exit_code != 0, result.output assert ( @@ -216,25 +216,23 @@ def test_errors_without_apio_ini_2(click_cmd_runner, setup_apio_test_env): ) -def test_errors_with_apio_ini( - click_cmd_runner, setup_apio_test_env, write_apio_ini -): +def test_errors_with_apio_ini(apio_runner: ApioRunner): """Test: apio build with apio create""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Write apio.ini - write_apio_ini({"board": "icezum", "top-module": "main"}) + apio_runner.write_apio_ini({"board": "icezum", "top-module": "main"}) # apio build - result = click_cmd_runner.invoke(cmd_build) + result = apio_runner.invoke(apio_build) assert result.exit_code != 0, result.output # apio build --board icestick - result = click_cmd_runner.invoke(cmd_build, ["--board", "icestick"]) + result = apio_runner.invoke(apio_build, ["--board", "icestick"]) assert result.exit_code != 0, result.output assert ( "Info: ignoring board specification from apio.ini." @@ -242,9 +240,7 @@ def test_errors_with_apio_ini( ) # apio build --fpga iCE40-HX1K-VQ100 - result = click_cmd_runner.invoke( - cmd_build, ["--fpga", "iCE40-HX1K-VQ100"] - ) + result = apio_runner.invoke(apio_build, ["--fpga", "iCE40-HX1K-VQ100"]) assert result.exit_code != 0, result.output assert ( "Error: contradictory argument values: 'fpga' = " @@ -252,8 +248,8 @@ def test_errors_with_apio_ini( ) # apio build --type lp --size 8k --pack cm225:4k - result = click_cmd_runner.invoke( - cmd_build, ["--type", "lp", "--size", "8k", "--pack", "cm225:4k"] + result = apio_runner.invoke( + apio_build, ["--type", "lp", "--size", "8k", "--pack", "cm225:4k"] ) assert result.exit_code != 0, result.output assert ( @@ -262,8 +258,8 @@ def test_errors_with_apio_ini( ) # apio build --type lp --size 8k - result = click_cmd_runner.invoke( - cmd_build, ["--type", "lp", "--size", "8k"] + result = apio_runner.invoke( + apio_build, ["--type", "lp", "--size", "8k"] ) assert result.exit_code != 0, result.output assert ( diff --git a/test/commands/test_clean.py b/test/commands/test_clean.py index 1eff97c0..20ce56c3 100644 --- a/test/commands/test_clean.py +++ b/test/commands/test_clean.py @@ -2,22 +2,26 @@ Test for the "apio clean" command """ +from pathlib import Path +from test.conftest import ApioRunner + + # -- apio clean entry point -from apio.commands.clean import cli as cmd_clean +from apio.commands.clean import cli as apio_clean -def test_clean_no_apio_ini_no_params(click_cmd_runner, setup_apio_test_env): +def test_clean_no_apio_ini_no_params(apio_runner: ApioRunner): """Test: apio clean when no apio.ini file is given No additional parameters are given """ - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio clean" - result = click_cmd_runner.invoke(cmd_clean) + result = apio_runner.invoke(apio_clean) # -- It is an error. Exit code should not be 0 assert result.exit_code != 0, result.output @@ -25,70 +29,66 @@ def test_clean_no_apio_ini_no_params(click_cmd_runner, setup_apio_test_env): assert "Error: insufficient arguments: missing board" in result.output # -- Execute "apio clean --board alhambra-ii" - result = click_cmd_runner.invoke(cmd_clean, ["--board", "alhambra-ii"]) + result = apio_runner.invoke(apio_clean, ["--board", "alhambra-ii"]) assert result.exit_code == 0, result.output -def test_clean_no_apio_ini_params( - click_cmd_runner, setup_apio_test_env, path_in_project -): +def test_clean_no_apio_ini_params(apio_runner: ApioRunner): """Test: apio clean when no apio.ini file is given. Board definition comes from --board parameter. """ - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Create a legacy artifact file. - path_in_project("main_tb.vcd").touch() + Path("main_tb.vcd").touch() # -- Create a current artifact file. - path_in_project("_build").mkdir() - path_in_project("_build/main_tb.vcd").touch() + Path("_build").mkdir() + Path("_build/main_tb.vcd").touch() # Confirm that the files exists - assert path_in_project("main_tb.vcd").is_file() - assert path_in_project("_build/main_tb.vcd").is_file() + assert Path("main_tb.vcd").is_file() + assert Path("_build/main_tb.vcd").is_file() # -- Execute "apio clean --board alhambra-ii" - result = click_cmd_runner.invoke(cmd_clean, ["--board", "alhambra-ii"]) + result = apio_runner.invoke(apio_clean, ["--board", "alhambra-ii"]) assert result.exit_code == 0, result.output # Confirm that the files do not exist. - assert not path_in_project("main_tb.vcd").exists() - assert not path_in_project("_build/main_tb.vcd").exists() + assert not Path("main_tb.vcd").exists() + assert not Path("_build/main_tb.vcd").exists() -def test_clean_create( - click_cmd_runner, setup_apio_test_env, path_in_project, write_apio_ini -): +def test_clean_create(apio_runner: ApioRunner): """Test: apio clean when there is an apio.ini file""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Create apio.ini - write_apio_ini({"board": "icezum", "top-module": "main"}) + apio_runner.write_apio_ini({"board": "icezum", "top-module": "main"}) # -- Create a legacy artifact file. - path_in_project("main_tb.vcd").touch() + Path("main_tb.vcd").touch() # -- Create a current artifact file. - path_in_project("_build").mkdir() - path_in_project("_build/main_tb.vcd").touch() + Path("_build").mkdir() + Path("_build/main_tb.vcd").touch() # Confirm that the files exists - assert path_in_project("main_tb.vcd").is_file() - assert path_in_project("_build/main_tb.vcd").is_file() + assert Path("main_tb.vcd").is_file() + assert Path("_build/main_tb.vcd").is_file() # --- Execute "apio clean" - result = click_cmd_runner.invoke(cmd_clean) + result = apio_runner.invoke(apio_clean) assert result.exit_code == 0, result.output # Confirm that the files do not exist. - assert not path_in_project("main_tb.vcd").exists() - assert not path_in_project("_build/main_tb.vcd").exists() + assert not Path("main_tb.vcd").exists() + assert not Path("_build/main_tb.vcd").exists() diff --git a/test/commands/test_create.py b/test/commands/test_create.py index a217cdd2..07fba45e 100644 --- a/test/commands/test_create.py +++ b/test/commands/test_create.py @@ -5,10 +5,12 @@ from pathlib import Path from os.path import isfile, exists from typing import Dict +from test.conftest import ApioRunner from configobj import ConfigObj + # -- apio create entry point -from apio.commands.create import cli as cmd_create +from apio.commands.create import cli as apio_create # R0801: Similar lines in 2 files @@ -24,34 +26,32 @@ def _check_ini_file(apio_ini: Path, expected_vars: Dict[str, str]) -> None: assert conf.dict() == {"env": expected_vars} -def test_create(click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok): +def test_create(apio_runner: ApioRunner): """Test "apio create" with different parameters""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() apio_ini = Path("apio.ini") assert not exists(apio_ini) # -- Execute "apio create" - result = click_cmd_runner.invoke(cmd_create) + result = apio_runner.invoke(apio_create) assert result.exit_code != 0, result.output assert "Error: Missing option" in result.output assert not exists(apio_ini) # -- Execute "apio create --board missed_board" - result = click_cmd_runner.invoke( - cmd_create, ["--board", "missed_board"] - ) + result = apio_runner.invoke(apio_create, ["--board", "missed_board"]) assert result.exit_code == 1, result.output assert "Error: no such board" in result.output assert not exists(apio_ini) # -- Execute "apio create --board icezum" - result = click_cmd_runner.invoke(cmd_create, ["--board", "icezum"]) - assert_apio_cmd_ok(result) + result = apio_runner.invoke(apio_create, ["--board", "icezum"]) + apio_runner.assert_ok(result) assert "file already exists" not in result.output assert "Do you want to replace it?" not in result.output assert "Creating apio.ini file ..." in result.output @@ -60,12 +60,12 @@ def test_create(click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok): # -- Execute "apio create --board alhambra-ii # -- --top-module my_module" with 'y' input" - result = click_cmd_runner.invoke( - cmd_create, + result = apio_runner.invoke( + apio_create, ["--board", "alhambra-ii", "--top-module", "my_module"], input="y", ) - assert_apio_cmd_ok(result) + apio_runner.assert_ok(result) assert "Warning" in result.output assert "file already exists" in result.output assert "Do you want to replace it?" in result.output @@ -77,11 +77,11 @@ def test_create(click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok): # -- Execute "apio create --board icezum # -- --top-module my_module # -- --sayyse" with 'y' input - result = click_cmd_runner.invoke( - cmd_create, + result = apio_runner.invoke( + apio_create, ["--board", "icezum", "--top-module", "my_module", "--sayyes"], ) - assert_apio_cmd_ok(result) + apio_runner.assert_ok(result) assert "was created successfully." in result.output _check_ini_file( apio_ini, {"board": "icezum", "top-module": "my_module"} @@ -89,8 +89,8 @@ def test_create(click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok): # -- Execute "apio create --board alhambra-ii # -- --top-module my_module" with 'n' input - result = click_cmd_runner.invoke( - cmd_create, + result = apio_runner.invoke( + apio_create, ["--board", "alhambra-ii", "--top-module", "my_module"], input="n", ) diff --git a/test/commands/test_drivers.py b/test/commands/test_drivers.py index a743c591..229ed72a 100644 --- a/test/commands/test_drivers.py +++ b/test/commands/test_drivers.py @@ -2,28 +2,34 @@ Test for the "apio drivers" command """ +from test.conftest import ApioRunner + # -- apio drivers entry point -from apio.commands.drivers import cli as cmd_drivers +from apio.commands.drivers import cli as apio_drivers -def test_drivers(click_cmd_runner, assert_apio_cmd_ok, setup_apio_test_env): +def test_drivers(apio_runner: ApioRunner): """Test "apio drivers" """ - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio drivers" - result = click_cmd_runner.invoke(cmd_drivers) - assert_apio_cmd_ok(result) + result = apio_runner.invoke(apio_drivers) + assert result.exit_code == 1 + assert ( + "Error: Specify one of [--ftdi-install, --ftdi-uninstall, " + "--serial-install, --serial-uninstall]" in result.output + ) # -- Execute "apio --ftdi-install, --serial-install" - result = click_cmd_runner.invoke( - cmd_drivers, ["--ftdi-install", "--serial-install"] + result = apio_runner.invoke( + apio_drivers, ["--ftdi-install", "--serial-install"] ) assert result.exit_code == 1, result.output assert ( - "Error: [--ftdi-install, --serial-install] " - "are mutually exclusive" in result.output + "Error: Specify only one of [--ftdi-install, --serial-install]" + in result.output ) diff --git a/test/commands/test_examples.py b/test/commands/test_examples.py index 8592a7e8..ad0e92c2 100644 --- a/test/commands/test_examples.py +++ b/test/commands/test_examples.py @@ -2,42 +2,42 @@ Test for the "apio examples" command """ +from test.conftest import ApioRunner + # -- apio examples entry point -from apio.commands.examples import cli as cmd_examples +from apio.commands.examples import cli as apio_examples -def test_examples(click_cmd_runner, setup_apio_test_env): +def test_examples(apio_runner: ApioRunner): """Test "apio examples" with different parameters""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio examples" - result = click_cmd_runner.invoke(cmd_examples) + result = apio_runner.invoke(apio_examples) assert result.exit_code == 1, result.output assert ( - "One of [--list, --fetch-dir, --fetch-files] " - "must be specified" in result.output + "Error: Specify one of [--list, --fetch-dir, --fetch-files]" + in result.output ) # -- Execute "apio examples --list" - result = click_cmd_runner.invoke(cmd_examples, ["--list"]) + result = apio_runner.invoke(apio_examples, ["--list"]) assert result.exit_code == 1, result.output assert "Error: package 'examples' is not installed" in result.output assert "apio packages --install --force examples" in result.output # -- Execute "apio examples --fetch-dir dir" - result = click_cmd_runner.invoke(cmd_examples, ["--fetch-dir", "dir"]) + result = apio_runner.invoke(apio_examples, ["--fetch-dir", "dir"]) assert result.exit_code == 1, result.output assert "Error: package 'examples' is not installed" in result.output assert "apio packages --install --force examples" in result.output # -- Execute "apio examples --files file" - result = click_cmd_runner.invoke( - cmd_examples, ["--fetch-files", "file"] - ) + result = apio_runner.invoke(apio_examples, ["--fetch-files", "file"]) assert result.exit_code == 1, result.output assert "Error: package 'examples' is not installed" in result.output assert "apio packages --install --force examples" in result.output diff --git a/test/commands/test_fpgas.py b/test/commands/test_fpgas.py index 58bf3704..0da5e824 100644 --- a/test/commands/test_fpgas.py +++ b/test/commands/test_fpgas.py @@ -2,8 +2,10 @@ Test for the "apio boards" command """ +from test.conftest import ApioRunner + # -- apio fpgas entry point -from apio.commands.fpgas import cli as cmd_fpgas +from apio.commands.fpgas import cli as apio_fpgas CUSTOM_FPGAS = """ { @@ -17,17 +19,17 @@ """ -def test_fpgas_ok(click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok): +def test_fpgas_ok(apio_runner: ApioRunner): """Test "apio fpgas" command with standard fpgas.json.""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio fpgas" - result = click_cmd_runner.invoke(cmd_fpgas) - assert_apio_cmd_ok(result) + result = apio_runner.invoke(apio_fpgas) + apio_runner.assert_ok(result) # -- Note: pytest sees the piped version of the command's output. # -- Run 'apio fpgas' | cat' to reproduce it. assert "Loading custom 'fpgas.json'" not in result.output @@ -35,23 +37,21 @@ def test_fpgas_ok(click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok): assert "my_custom_fpga" not in result.output -def test_custom_fpga( - click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok -): +def test_custom_fpga(apio_runner: ApioRunner): """Test "apio fpgas" command with a custom fpgas.json.""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Write a custom boards.json file in the project's directory. with open("fpgas.json", "w", encoding="utf-8") as f: f.write(CUSTOM_FPGAS) # -- Execute "apio boards" - result = click_cmd_runner.invoke(cmd_fpgas) - assert_apio_cmd_ok(result) + result = apio_runner.invoke(apio_fpgas) + apio_runner.assert_ok(result) # -- Note: pytest sees the piped version of the command's output. # -- Run 'apio build' | cat' to reproduce it. assert "Loading custom 'fpgas.json'" in result.output diff --git a/test/commands/test_graph.py b/test/commands/test_graph.py index ad8fe832..05c89e4b 100644 --- a/test/commands/test_graph.py +++ b/test/commands/test_graph.py @@ -2,51 +2,51 @@ Test for the "apio graph" command """ +from test.conftest import ApioRunner + # -- apio graph entry point -from apio.commands.graph import cli as cmd_graph +from apio.commands.graph import cli as apio_graph -def test_graph_no_apio_ini(click_cmd_runner, setup_apio_test_env): +def test_graph_no_apio_ini(apio_runner: ApioRunner): """Test: apio graph with no apio.ini""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio graph" - result = click_cmd_runner.invoke(cmd_graph) + result = apio_runner.invoke(apio_graph) assert result.exit_code == 1, result.output assert "Error: insufficient arguments: missing board" in result.output -def test_graph_with_apio_ini( - click_cmd_runner, setup_apio_test_env, write_apio_ini -): +def test_graph_with_apio_ini(apio_runner: ApioRunner): """Test: apio graph with apio.ini""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Create an apio.ini file - write_apio_ini({"board": "icezum", "top-module": "main"}) + apio_runner.write_apio_ini({"board": "icezum", "top-module": "main"}) # -- Execute "apio graph" - result = click_cmd_runner.invoke(cmd_graph) + result = apio_runner.invoke(apio_graph) assert result.exit_code == 1, result.output assert "package 'oss-cad-suite' is not installed" in result.output assert "apio packages --install --force oss-cad-suite" in result.output # -- Execute "apio graph -pdf" - result = click_cmd_runner.invoke(cmd_graph) + result = apio_runner.invoke(apio_graph) assert result.exit_code == 1, result.output assert "package 'oss-cad-suite' is not installed" in result.output assert "apio packages --install --force oss-cad-suite" in result.output # -- Execute "apio graph -png" - result = click_cmd_runner.invoke(cmd_graph) + result = apio_runner.invoke(apio_graph) assert result.exit_code == 1, result.output assert "package 'oss-cad-suite' is not installed" in result.output assert "apio packages --install --force oss-cad-suite" in result.output diff --git a/test/commands/test_install.py b/test/commands/test_install.py index 8db4ca2c..b931391b 100644 --- a/test/commands/test_install.py +++ b/test/commands/test_install.py @@ -2,27 +2,29 @@ Test for the "apio install" command """ +from test.conftest import ApioRunner + # -- apio install entry point -from apio.commands.install import cli as cmd_install +from apio.commands.install import cli as apio_install -def test_install(click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok): +def test_install(apio_runner: ApioRunner): """Test "apio install" with different parameters""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio install" - result = click_cmd_runner.invoke(cmd_install) - assert_apio_cmd_ok(result) + result = apio_runner.invoke(apio_install) + apio_runner.assert_ok(result) # -- Execute "apio install --list" - result = click_cmd_runner.invoke(cmd_install, ["--list"]) - assert_apio_cmd_ok(result) + result = apio_runner.invoke(apio_install, ["--list"]) + apio_runner.assert_ok(result) # -- Execute "apio install missing_package" - result = click_cmd_runner.invoke(cmd_install, ["missing_package"]) + result = apio_runner.invoke(apio_install, ["missing_package"]) assert result.exit_code == 1, result.output assert "Error: no such package" in result.output diff --git a/test/commands/test_lint.py b/test/commands/test_lint.py index 61a7cb83..fe72bd37 100644 --- a/test/commands/test_lint.py +++ b/test/commands/test_lint.py @@ -2,25 +2,25 @@ Test for the "apio lint" command """ +from test.conftest import ApioRunner + # -- apio lint entry point -from apio.commands.lint import cli as cmd_lint +from apio.commands.lint import cli as apio_lint -def test_lint_no_packages( - click_cmd_runner, setup_apio_test_env, write_apio_ini -): +def test_lint_no_packages(apio_runner: ApioRunner): """Test: apio lint with missing packages.""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Create apio.ini file. - write_apio_ini({"board": "icezum", "top-module": "main"}) + apio_runner.write_apio_ini({"board": "icezum", "top-module": "main"}) # -- Execute "apio lint" - result = click_cmd_runner.invoke(cmd_lint) + result = apio_runner.invoke(apio_lint) assert result.exit_code == 1, result.output assert ( "Error: package 'oss-cad-suite' is not installed" in result.output diff --git a/test/commands/test_modify.py b/test/commands/test_modify.py index 92aed60f..3600c59a 100644 --- a/test/commands/test_modify.py +++ b/test/commands/test_modify.py @@ -5,10 +5,12 @@ from pathlib import Path from os.path import isfile, exists from typing import Dict +from test.conftest import ApioRunner from configobj import ConfigObj + # -- apio modify entry point -from apio.commands.modify import cli as cmd_modify +from apio.commands.modify import cli as apio_modify # R0801: Similar lines in 2 files @@ -24,21 +26,19 @@ def check_ini_file(apio_ini: Path, expected_vars: Dict[str, str]) -> None: assert conf.dict() == {"env": expected_vars} -def test_modify(click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok): +def test_modify(apio_runner: ApioRunner): """Test "apio modify" with different parameters""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() apio_ini = Path("apio.ini") assert not exists(apio_ini) # -- Execute "apio modify --top-module my_module" - result = click_cmd_runner.invoke( - cmd_modify, ["--top-module", "my_module"] - ) + result = apio_runner.invoke(apio_modify, ["--top-module", "my_module"]) assert result.exit_code != 0, result.output assert "Error: 'apio.ini' not found" in result.output assert not exists(apio_ini) @@ -62,9 +62,7 @@ def test_modify(click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok): ) # -- Execute "apio modify --board missed_board" - result = click_cmd_runner.invoke( - cmd_modify, ["--board", "missed_board"] - ) + result = apio_runner.invoke(apio_modify, ["--board", "missed_board"]) assert result.exit_code == 1, result.output assert "Error: no such board" in result.output check_ini_file( @@ -77,10 +75,8 @@ def test_modify(click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok): ) # -- Execute "apio modify --board alhambra-ii" - result = click_cmd_runner.invoke( - cmd_modify, ["--board", "alhambra-ii"] - ) - assert_apio_cmd_ok(result) + result = apio_runner.invoke(apio_modify, ["--board", "alhambra-ii"]) + apio_runner.assert_ok(result) assert "was modified successfully." in result.output check_ini_file( apio_ini, @@ -92,10 +88,8 @@ def test_modify(click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok): ) # -- Execute "apio modify --top-module my_main" - result = click_cmd_runner.invoke( - cmd_modify, ["--top-module", "my_main"] - ) - assert_apio_cmd_ok(result) + result = apio_runner.invoke(apio_modify, ["--top-module", "my_main"]) + apio_runner.assert_ok(result) assert "was modified successfully." in result.output check_ini_file( apio_ini, @@ -107,10 +101,10 @@ def test_modify(click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok): ) # -- Execute "apio modify --board icezum --top-module my_top" - result = click_cmd_runner.invoke( - cmd_modify, ["--board", "icezum", "--top-module", "my_top"] + result = apio_runner.invoke( + apio_modify, ["--board", "icezum", "--top-module", "my_top"] ) - assert_apio_cmd_ok(result) + apio_runner.assert_ok(result) assert "was modified successfully." in result.output check_ini_file( apio_ini, diff --git a/test/commands/test_packages.py b/test/commands/test_packages.py index 75f7db79..f7b2fbb3 100644 --- a/test/commands/test_packages.py +++ b/test/commands/test_packages.py @@ -2,41 +2,43 @@ Test for the "apio packages" command """ +from test.conftest import ApioRunner + # -- apio packages entry point -from apio.commands.packages import cli as cmd_packages +from apio.commands.packages import cli as apio_packages -def test_packages(click_cmd_runner, setup_apio_test_env): +def test_packages(apio_runner: ApioRunner): """Test "apio packages" with different parameters""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio packages" - result = click_cmd_runner.invoke(cmd_packages) + result = apio_runner.invoke(apio_packages) assert result.exit_code == 1, result.output assert ( - "One of [--list, --install, --uninstall, --fix] " - "must be specified" in result.output + "Error: Specify one of [--list, --install, --uninstall, --fix]" + in result.output ) # -- Execute "apio packages --list" - result = click_cmd_runner.invoke(cmd_packages, ["--list"]) + result = apio_runner.invoke(apio_packages, ["--list"]) assert result.exit_code == 0, result.output assert "No errors" in result.output # -- Execute "apio packages --install missing_package" - result = click_cmd_runner.invoke( - cmd_packages, ["--install", "missing_package"] + result = apio_runner.invoke( + apio_packages, ["--install", "missing_package"] ) assert result.exit_code == 1, result.output assert "Error: unknown package 'missing_package'" in result.output # -- Execute "apio packages --uninstall --sayyes missing_package" - result = click_cmd_runner.invoke( - cmd_packages, ["--uninstall", "--sayyes", "missing_package"] + result = apio_runner.invoke( + apio_packages, ["--uninstall", "--sayyes", "missing_package"] ) assert result.exit_code == 1, result.output assert "Error: no such package 'missing_package'" in result.output diff --git a/test/commands/test_report.py b/test/commands/test_report.py index c0bf80fb..512c04fe 100644 --- a/test/commands/test_report.py +++ b/test/commands/test_report.py @@ -2,25 +2,27 @@ Test for the "apio report" command """ +from test.conftest import ApioRunner + # -- apio report entry point -from apio.commands.report import cli as cmd_report +from apio.commands.report import cli as apio_report # R0801: Similar lines in 2 files # pylint: disable=R0801 -def test_report(click_cmd_runner, setup_apio_test_env): +def test_report(apio_runner: ApioRunner): """Test: apio report when no apio.ini file is given No additional parameters are given """ - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio report" - result = click_cmd_runner.invoke(cmd_report) + result = apio_runner.invoke(apio_report) # -- Check the result assert result.exit_code != 0, result.output @@ -28,18 +30,18 @@ def test_report(click_cmd_runner, setup_apio_test_env): assert "Error: insufficient arguments: missing board" in result.output -def test_report_board(click_cmd_runner, setup_apio_test_env): +def test_report_board(apio_runner: ApioRunner): """Test: apio report when parameters are given """ - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio report" - result = click_cmd_runner.invoke(cmd_report, ["--board", "icezum"]) + result = apio_runner.invoke(apio_report, ["--board", "icezum"]) # -- Check the result assert result.exit_code != 0, result.output diff --git a/test/commands/test_sim.py b/test/commands/test_sim.py index 87350f06..53c69b7d 100644 --- a/test/commands/test_sim.py +++ b/test/commands/test_sim.py @@ -2,22 +2,24 @@ Test for the "apio sim" command """ +from test.conftest import ApioRunner + # -- apio sim entry point -from apio.commands.sim import cli as cmd_sim +from apio.commands.sim import cli as apio_sim -def test_sim(click_cmd_runner, setup_apio_test_env): +def test_sim(apio_runner: ApioRunner): """Test: apio sim when no apio.ini file is given No additional parameters are given """ - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- apio sim --board icezum - result = click_cmd_runner.invoke(cmd_sim) + result = apio_runner.invoke(apio_sim) assert result.exit_code != 0, result.output # -- TODO diff --git a/test/commands/test_system.py b/test/commands/test_system.py index 2b969eeb..3392c8be 100644 --- a/test/commands/test_system.py +++ b/test/commands/test_system.py @@ -2,43 +2,46 @@ Test for the "apio system" command """ +from test.conftest import ApioRunner + # -- apio system entry point -from apio.commands.system import cli as cmd_system +from apio.commands.system import cli as apio_system -def test_system(click_cmd_runner, setup_apio_test_env): +def test_system(apio_runner: ApioRunner): """Test "apio system" with different parameters""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio system" - result = click_cmd_runner.invoke(cmd_system) + result = apio_runner.invoke(apio_system) assert result.exit_code == 1, result.output assert ( - "One of [--lsftdi, --lsusb, --lsserial, --info, --platforms] " - "must be specified" in result.output + "Specify one of " + "[--lsftdi, --lsusb, --lsserial, --info, --platforms]" + in result.output ) # -- Execute "apio system --lsftdi" - result = click_cmd_runner.invoke(cmd_system, ["--lsftdi"]) + result = apio_runner.invoke(apio_system, ["--lsftdi"]) assert result.exit_code == 1, result.output assert "apio packages --install --force oss-cad-suite" in result.output # -- Execute "apio system --lsusb" - result = click_cmd_runner.invoke(cmd_system, ["--lsusb"]) + result = apio_runner.invoke(apio_system, ["--lsusb"]) assert result.exit_code == 1, result.output assert "apio packages --install --force oss-cad-suite" in result.output # -- Execute "apio system --lsserial" - click_cmd_runner.invoke(cmd_system, ["--lsserial"]) + apio_runner.invoke(apio_system, ["--lsserial"]) assert result.exit_code == 1, result.output assert "apio packages --install --force oss-cad-suite" in result.output # -- Execute "apio system --info" - result = click_cmd_runner.invoke(cmd_system, ["--info"]) + result = apio_runner.invoke(apio_system, ["--info"]) assert result.exit_code == 0, result.output assert "Platform id" in result.output # -- The these env options are set by the apio text fixture. diff --git a/test/commands/test_test.py b/test/commands/test_test.py index 36cdca27..a50d0236 100644 --- a/test/commands/test_test.py +++ b/test/commands/test_test.py @@ -2,22 +2,24 @@ Test for the "apio test" command """ +from test.conftest import ApioRunner + # -- apio test entry point -from apio.commands.sim import cli as cmd_test +from apio.commands.sim import cli as apio_test -def test_test(click_cmd_runner, setup_apio_test_env): +def test_test(apio_runner: ApioRunner): """Test: apio test when no apio.ini file is given No additional parameters are given """ - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio test" - result = click_cmd_runner.invoke(cmd_test) + result = apio_runner.invoke(apio_test) assert result.exit_code != 0, result.output # -- TODO diff --git a/test/commands/test_time.py b/test/commands/test_time.py index ed083c9c..91b4cafd 100644 --- a/test/commands/test_time.py +++ b/test/commands/test_time.py @@ -2,25 +2,27 @@ Test for the "apio time" command """ +from test.conftest import ApioRunner + # -- apio time entry point -from apio.commands.time import cli as cmd_time +from apio.commands.time import cli as apio_time # R0801: Similar lines in 2 files # pylint: disable=R0801 -def test_time(click_cmd_runner, setup_apio_test_env): +def test_time(apio_runner: ApioRunner): """Test: apio time when no apio.ini file is given No additional parameters are given """ - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio time" - result = click_cmd_runner.invoke(cmd_time) + result = apio_runner.invoke(apio_time) # -- Check the result assert result.exit_code != 0, result.output @@ -28,18 +30,18 @@ def test_time(click_cmd_runner, setup_apio_test_env): assert "Error: insufficient arguments: missing board" in result.output -def test_time_board(click_cmd_runner, setup_apio_test_env): +def test_time_board(apio_runner: ApioRunner): """Test: apio time when parameters are given """ - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio time" - result = click_cmd_runner.invoke(cmd_time, ["--board", "icezum"]) + result = apio_runner.invoke(apio_time, ["--board", "icezum"]) # -- Check the result assert result.exit_code != 0, result.output diff --git a/test/commands/test_uninstall.py b/test/commands/test_uninstall.py index d56c8b6a..790152b7 100644 --- a/test/commands/test_uninstall.py +++ b/test/commands/test_uninstall.py @@ -2,29 +2,31 @@ Test for the "apio uninstall" command """ +from test.conftest import ApioRunner + # -- apio uninstall entry point -from apio.commands.uninstall import cli as cmd_uninstall +from apio.commands.uninstall import cli as apio_uninstall -def test_uninstall(click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok): +def test_uninstall(apio_runner: ApioRunner): """Test "apio uninstall" with different parameters""" - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio uninstall" - result = click_cmd_runner.invoke(cmd_uninstall) - assert_apio_cmd_ok(result) + result = apio_runner.invoke(apio_uninstall) + apio_runner.assert_ok(result) # -- Execute "apio uninstall --list" - result = click_cmd_runner.invoke(cmd_uninstall, ["--list"]) - assert_apio_cmd_ok(result) + result = apio_runner.invoke(apio_uninstall, ["--list"]) + apio_runner.assert_ok(result) # -- Execute "apio uninstall missing_packge" - result = click_cmd_runner.invoke( - cmd_uninstall, ["missing_package"], input="y" + result = apio_runner.invoke( + apio_uninstall, ["missing_package"], input="y" ) assert result.exit_code == 1, result.output assert "Do you want to uninstall?" in result.output diff --git a/test/commands/test_upgrade.py b/test/commands/test_upgrade.py index 987fc9a7..c4a91eea 100644 --- a/test/commands/test_upgrade.py +++ b/test/commands/test_upgrade.py @@ -2,27 +2,27 @@ Test for the "apio upgrade" command """ +from test.conftest import ApioRunner import pytest + # -- apio upgrade entry point -from apio.commands.upgrade import cli as cmd_upgrade +from apio.commands.upgrade import cli as apio_upgrade -def test_upgrade( - click_cmd_runner, setup_apio_test_env, assert_apio_cmd_ok, offline_flag -): +def test_upgrade(apio_runner: ApioRunner): """Test "apio upgrade" """ # -- If the option 'offline' is passed, the test is skip # -- (apio upgrade uses internet) - if offline_flag: + if apio_runner.offline_flag: pytest.skip("requires internet connection") - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio upgrade" - result = click_cmd_runner.invoke(cmd_upgrade) - assert_apio_cmd_ok(result) + result = apio_runner.invoke(apio_upgrade) + apio_runner.assert_ok(result) diff --git a/test/commands/test_upload.py b/test/commands/test_upload.py index 932ae650..8a7a1522 100644 --- a/test/commands/test_upload.py +++ b/test/commands/test_upload.py @@ -2,23 +2,25 @@ Test for the "apio upload" command """ +from test.conftest import ApioRunner + # -- apio time entry point -from apio.commands.upload import cli as cmd_upload +from apio.commands.upload import cli as apio_upload -def test_upload(click_cmd_runner, setup_apio_test_env): +def test_upload(apio_runner: ApioRunner): """Test: apio upload when no apio.ini file is given No additional parameters are given """ - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio upload" - result = click_cmd_runner.invoke(cmd_upload) + result = apio_runner.invoke(apio_upload) # -- Check the result assert result.exit_code == 1, result.output @@ -26,18 +28,18 @@ def test_upload(click_cmd_runner, setup_apio_test_env): assert "Error: insufficient arguments: missing board" in result.output -def test_upload_board(click_cmd_runner, setup_apio_test_env): +def test_upload_board(apio_runner: ApioRunner): """Test: apio upload --board icezum No oss-cad-suite package is installed """ - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio upload --board icezum" - result = click_cmd_runner.invoke(cmd_upload, ["--board", "icezum"]) + result = apio_runner.invoke(apio_upload, ["--board", "icezum"]) # -- Check the result assert result.exit_code == 1 @@ -46,37 +48,37 @@ def test_upload_board(click_cmd_runner, setup_apio_test_env): ) -def test_upload_complete(click_cmd_runner, setup_apio_test_env): +def test_upload_complete(apio_runner: ApioRunner): """Test: apio upload with different arguments No apio.ini file is given """ - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio upload --serial-port COM0" - result = click_cmd_runner.invoke(cmd_upload, ["--serial-port", "COM0"]) + result = apio_runner.invoke(apio_upload, ["--serial-port", "COM0"]) assert result.exit_code == 1, result.output assert "Info: Project has no apio.ini file" in result.output assert "Error: insufficient arguments: missing board" in result.output # -- Execute "apio upload --ftdi-id 0" - result = click_cmd_runner.invoke(cmd_upload, ["--ftdi-id", "0"]) + result = apio_runner.invoke(apio_upload, ["--ftdi-id", "0"]) assert result.exit_code == 1, result.output assert "Info: Project has no apio.ini file" in result.output assert "Error: insufficient arguments: missing board" in result.output # -- Execute "apio upload --sram" - result = click_cmd_runner.invoke(cmd_upload, ["--sram"]) + result = apio_runner.invoke(apio_upload, ["--sram"]) assert result.exit_code == 1, result.output assert "Info: Project has no apio.ini file" in result.output assert "Error: insufficient arguments: missing board" in result.output # -- Execute "apio upload --board icezum --serial-port COM0" - result = click_cmd_runner.invoke( - cmd_upload, ["--board", "icezum", "--serial-port", "COM0"] + result = apio_runner.invoke( + apio_upload, ["--board", "icezum", "--serial-port", "COM0"] ) assert result.exit_code == 1, result.output assert ( @@ -84,8 +86,8 @@ def test_upload_complete(click_cmd_runner, setup_apio_test_env): ) # -- Execute "apio upload --board icezum --ftdi-id 0" - result = click_cmd_runner.invoke( - cmd_upload, ["--board", "icezum", "--ftdi-id", "0"] + result = apio_runner.invoke( + apio_upload, ["--board", "icezum", "--ftdi-id", "0"] ) assert result.exit_code == 1, result.output assert ( @@ -93,8 +95,8 @@ def test_upload_complete(click_cmd_runner, setup_apio_test_env): ) # -- Execute "apio upload --board icezum --sram" - result = click_cmd_runner.invoke( - cmd_upload, ["--board", "icezum", "--sram"] + result = apio_runner.invoke( + apio_upload, ["--board", "icezum", "--sram"] ) assert result.exit_code == 1, result.output assert ( diff --git a/test/commands/test_verify.py b/test/commands/test_verify.py index 7acb82c9..e8da9c8d 100644 --- a/test/commands/test_verify.py +++ b/test/commands/test_verify.py @@ -2,23 +2,25 @@ Test for the "apio verify" command """ +from test.conftest import ApioRunner + # -- apio verify entry point -from apio.commands.verify import cli as cmd_verify +from apio.commands.verify import cli as apio_verify -def test_verify(click_cmd_runner, setup_apio_test_env): +def test_verify(apio_runner: ApioRunner): """Test: apio verify when no apio.ini file is given No additional parameters are given """ - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio verify" - result = click_cmd_runner.invoke(cmd_verify, ["--board", "icezum"]) + result = apio_runner.invoke(apio_verify, ["--board", "icezum"]) # -- Check the result assert result.exit_code != 0, result.output diff --git a/test/conftest.py b/test/conftest.py index 8574d0dd..cba73513 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -3,11 +3,10 @@ TEST configuration file """ -# os.environ: Access to environment variables -# https://docs.python.org/3/library/os.html#os.environ -from os import environ, listdir from pathlib import Path +from typing import Tuple from typing import Dict +import os import pytest @@ -22,61 +21,155 @@ # -- Debug mode on/off DEBUG = True +# -- Apio should be able to handle spaces and unicode in its home, packages, +# -- and project directory path. We insert this marker in the test pathes to +# -- test it. +# +# -- TODO: Currently apio doesn't handle well spaces in the pathes. Fix it and +# -- change this to " fuññy ". For more details see +# -- https://github.com/FPGAwars/apio/issues/474. +FUNNY_MARKER = "fuññy" -@pytest.fixture(scope="module") -def click_cmd_runner(): - """A pytest fixture that provides tests with a click commands runner.""" - return CliRunner() +# -- This function is called by pytest. It addes the pytest --offline flag +# -- which is is passed to tests that ask for it using the fixture +# -- 'offline_flag' below. +# -- +# -- More info: https://docs.pytest.org/en/7.1.x/example/simple.html +def pytest_addoption(parser: pytest.Parser): + """Register the --offline command line option when invoking pytest""" -@pytest.fixture(scope="session") -def setup_apio_test_env(): - """An pytest fixture that provides tests with a function to set up the - apio test environment. By default, the tests run in a temporaly folder - (in /tmp/xxxxx). - """ - - def decorator(): - # -- Current directory is the project dir. - project_dir = Path.cwd() - - # -- Set a strange directory for executing - # -- apio: it contains spaces and unicode characters - # -- for testing. It should work - apio_home_dir = project_dir / " ñ" - apio_packages_dir = apio_home_dir / "packages" - - # -- Debug - if DEBUG: - print("") - print(" --> setup_apio_test_env():") - print(f" apio project dir : {str(project_dir)}") - print(f" apio home dir : {str(apio_home_dir)}") - print(f" apio packages dir : {str(apio_packages_dir)}") + # -- Option: --offline + # -- It is used by the function test that requieres + # -- internet connnection for testing + parser.addoption( + "--offline", action="store_true", help="Run tests in offline mode" + ) - # -- Since the test run in a fresh temp directory, we expect it to be - # -- empty with no left over files (such as apio.ini) from a previous - # -- tests. - project_dir_content = listdir(".") - assert not project_dir_content, project_dir_content - # -- Set the apio home dir and apio packages dir to - # -- this test folder - environ["APIO_HOME_DIR"] = str(apio_home_dir) - environ["APIO_PACKAGES_DIR"] = str(apio_packages_dir) - environ["TESTING"] = "" +class ApioRunner: + """Apio commands test helper. An object of this class is provided to the + tests via the apio_runner fixture and a typical tests looks like this: - return decorator + def test_my_cmd(apio_runner): + with apio_runner.isolated_filesystem(): + apio_runner.setup_env() -@pytest.fixture(scope="session") -def assert_apio_cmd_ok(): - """A pytest fixture that provides a function to assert that apio click - command result were ok. + """ - def decorator(result: Result): - """Check if the result is ok""" + def __init__(self, request): + # -- A CliRunner instance that is used for creating temp directories + # -- and to invoke apio commands. + self._request = request + self._click_runner = CliRunner() + + # -- Save the original system path so we can restore it before each + # -- invocation since some apio commands mutate it and pytest runs + # -- multiple apio command in the same python process. + self._original_path = os.environ["PATH"] + + # -- Set later by set_env(). + self._proj_dir: Path = None + self._home_dir: Path = None + self._packages_dir: Path = None + + def in_disposable_temp_dir(self): + """Returns a context manager which creates a temp directory upon + entering it and deleting it upon existing.""" + return self._click_runner.isolated_filesystem() + + def setup_env(self) -> Tuple[Path, Path, Path]: + """Should be called by the test within the 'in_disposable_temp_dir' + scope to set up the apio specific environment.""" + # -- Current directory is the root test dir. Should be empty. + test_dir = Path.cwd() + assert not os.listdir(test_dir) + + # -- Using unicode and space to verify that they are handled correctly. + funny_dir = test_dir / FUNNY_MARKER + funny_dir.mkdir(parents=False, exist_ok=False) + + # -- Apio dirs + self._proj_dir = funny_dir / "proj" + self._home_dir = funny_dir / "apio" + self._packages_dir = funny_dir / "packages" + + if DEBUG: + print("") + print(" --> apio_runner.setup_env():") + print(f" test dir : {str(test_dir)}") + print(f" apio proj dir : {str(self._proj_dir)}") + print(f" apio home dir : {str(self._home_dir)}") + print(f" apio packages dir : {str(self._packages_dir)}") + + # -- Apio dirs do not exist yet. + assert not self._proj_dir.exists() + assert not self._home_dir.exists() + assert not self._packages_dir.exists() + + # -- TODO: This looks like a flag. Describe what it do. + os.environ["TESTING"] = "" + + # -- All done, return the values. + return ( + self._proj_dir, + self._home_dir, + self._packages_dir, + ) + + # R0913: Too many arguments (7/5) (too-many-arguments) + # pylint: disable=R0913 + # W0622: Redefining built-in 'input' (redefined-builtin) + # pylint: disable=W0622 + # R0917: Too many positional arguments (7/5) + # pylint: disable=R0917 + def invoke( + self, + cli, + args=None, + input=None, + env=None, + catch_exceptions=True, + color=False, + **extra, + ): + """Invoke an apio command.""" + + # -- Restore the original path, since some apio commands mutate it and + # -- pytest runs multiple commands in the same python process. + os.environ["PATH"] = self._original_path + + # -- This typically fails if the test did not call setup_env() before4 + # -- invoking a command. + assert FUNNY_MARKER in str(self._home_dir) + assert FUNNY_MARKER in str(self._packages_dir) + + # -- Set the env to infrom the apio command where are the apio test + # -- home and packages dir are. + os.environ["APIO_HOME_DIR"] = str(self._home_dir) + os.environ["APIO_PACKAGES_DIR"] = str(self._packages_dir) + + # -- Double check. + assert FUNNY_MARKER in os.environ["APIO_HOME_DIR"] + assert FUNNY_MARKER in os.environ["APIO_PACKAGES_DIR"] + + # -- Invoke the command. Get back the collected results. + result = self._click_runner.invoke( + cli=cli, + args=args, + input=input, + env=env, + catch_exceptions=catch_exceptions, + color=color, + **extra, + ) + + return result + + def assert_ok(self, result: Result): + """Check if apio command results where ok""" # -- It should return an exit code of 0: success assert result.exit_code == 0, result.output @@ -87,17 +180,10 @@ def decorator(result: Result): # -- The word 'error' should NOT appear on the standard output assert "error" not in result.output.lower() - return decorator - - -@pytest.fixture(scope="session") -def write_apio_ini(): - """A pytest fixture to write a project apio.ini file. If properties - is Nonethe file apio.ini is deleted if it exists. - """ - - def decorator(properties: Dict[str, str]): - """The apio.ini actual writer""" + def write_apio_ini(self, properties: Dict[str, str]): + """Write in the current directory an apio.ini file with given + values. If an apio.ini file alread exists, it is overwritten. + if properties is None and an apio.ini file exists, it is deleted.""" path = Path("apio.ini") @@ -110,43 +196,15 @@ def decorator(properties: Dict[str, str]): for name, value in properties.items(): f.write(f"{name} = {value}\n") - return decorator + @property + def offline_flag(self) -> bool: + """Returns True if pytest was invoked with --offline to skip + tests that require internet connectivity and are slower in general.""" + return self._request.config.getoption("--offline") @pytest.fixture(scope="session") -def path_in_project(): - """A pytest fixutre that provides a function that convert a file name - to a path within the project. - """ - - def decorator(name: str): - """The implementation""" - - return Path.cwd() / name - - return decorator - - -# -- This function is called by pytest. It addes the pytest --offline flag -# -- which is is passed to tests that ask for it using the fixture -# -- 'offline_flag' below. -# -- -# -- More info: https://docs.pytest.org/en/7.1.x/example/simple.html -def pytest_addoption(parser: pytest.Parser): - """Register the --offline command line option when invoking pytest""" - - # -- Option: --offline - # -- It is used by the function test that requieres - # -- internet connnection for testing - parser.addoption( - "--offline", action="store_true", help="Run tests in offline mode" - ) - - -@pytest.fixture -def offline_flag(request): - """Return the value of the pytest '--offline' flag register above. - This flag can be set by the user when invoking pytest to disable - test functionality that requires internet connectivity. - """ - return request.config.getoption("--offline") +def apio_runner(request): + """A pytest fixture that provides tests with a ApioRunner test + helper object.""" + return ApioRunner(request) diff --git a/test/integration/__init__.py b/test/integration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/integration/test_examples.py b/test/integration/test_examples.py new file mode 100644 index 00000000..9bc87031 --- /dev/null +++ b/test/integration/test_examples.py @@ -0,0 +1,88 @@ +""" + Test different "apio" commands +""" + +from os import chdir +from os.path import getsize +from test.conftest import ApioRunner +import pytest + +# -- Entry point for apio commands. +from apio.commands.packages import cli as apio_packages +from apio.commands.examples import cli as apio_examples + + +# R0801: Similar lines in 2 files +# pylint: disable=R0801 +def test_examples(apio_runner: ApioRunner): + """Tests the listing and fetching apio examples.""" + + # -- If the option 'offline' is passed, the test is skip + # -- (This test is slow and requires internet connectivity) + if apio_runner.offline_flag: + pytest.skip("requires internet connection") + + with apio_runner.in_disposable_temp_dir(): + + # -- Config the apio test environment + proj_dir, _, packages_dir = apio_runner.setup_env() + + # -- Create and change to project dir. + proj_dir.mkdir(exist_ok=False) + chdir(proj_dir) + + # -- Install the examples package. + result = apio_runner.invoke(apio_packages, ["--install", "examples"]) + apio_runner.assert_ok(result) + # assert "Installing package 'examples'" in result.output + # assert "Download" in result.output + assert "Package 'examples' installed successfully" in result.output + assert getsize(packages_dir / "examples/Alhambra-II/ledon/ledon.v") + + # -- List the examples + result = apio_runner.invoke( + apio_examples, + ["--list"], + ) + apio_runner.assert_ok(result) + assert "Alhambra-II/ledon" in result.output + + # -- Fetch example files to current directory + result = apio_runner.invoke( + apio_examples, + ["--fetch-files", "Alhambra-II/ledon"], + ) + apio_runner.assert_ok(result) + assert "Copying Alhambra-II/ledon example files" in result.output + assert "have been successfully created!" in result.output + assert getsize("ledon.v") + + # -- Fetch example dir to current directory + result = apio_runner.invoke( + apio_examples, + ["--fetch-dir", "Alhambra-II/ledon"], + ) + apio_runner.assert_ok(result) + assert "Creating Alhambra-II/ledon directory" in result.output + assert "has been successfully created" in result.output + assert getsize("Alhambra-II/ledon/ledon.v") + + # -- Fetch example files to another project dir + result = apio_runner.invoke( + apio_examples, + ["--fetch-files", "Alhambra-II/ledon", "--project-dir=./dir1"], + ) + apio_runner.assert_ok(result) + assert "Copying Alhambra-II/ledon example files" in result.output + assert "have been successfully created!" in result.output + assert getsize("dir1/ledon.v") + + # -- Fetch example dir to another project dir + result = apio_runner.invoke( + apio_examples, + ["--fetch-dir", "Alhambra-II/ledon", "--project-dir=dir2"], + ) + apio_runner.assert_ok(result) + assert "Creating Alhambra-II/ledon directory" in result.output + assert "has been successfully created" in result.output + assert getsize("dir2/Alhambra-II/ledon/ledon.v") diff --git a/test/integration/test_packages.py b/test/integration/test_packages.py new file mode 100644 index 00000000..27fd6c7a --- /dev/null +++ b/test/integration/test_packages.py @@ -0,0 +1,114 @@ +""" + Test different "apio" commands +""" + +from os import listdir, chdir +from test.conftest import ApioRunner +import pytest + +# -- Entry point for apio commands. +from apio.commands.packages import cli as apio_packages + + +# R0801: Similar lines in 2 files +# pylint: disable=R0801 +# # R0915: Too many statements (52/50) (too-many-statements) +# # pylint: disable=R0915 +def test_packages(apio_runner: ApioRunner): + """Tests listing, installation and uninstallation of packages.""" + + # -- If the option 'offline' is passed, the test is skip + # -- (This test is slow and requires internet connectivity) + if apio_runner.offline_flag: + pytest.skip("requires internet connection") + + with apio_runner.in_disposable_temp_dir(): + + # -- Config the apio test environment + proj_dir, _, packages_dir = apio_runner.setup_env() + + # -- Create and change to project dir. + proj_dir.mkdir(exist_ok=False) + chdir(proj_dir) + + # -- List packages + result = apio_runner.invoke(apio_packages, ["--list"]) + assert result.exit_code == 0 + assert "No errors" in result.output + assert "examples" in result.output + assert "oss-cad-suite" in result.output + + # -- Packages dir doesn't exist yet. + assert not packages_dir.exists() + + # -- Install the examples package. Package 'examples' should exist, + # -- and package 'tools-oss-cad-suite' should not. + result = apio_runner.invoke(apio_packages, ["--install", "examples"]) + apio_runner.assert_ok(result) + assert "Package 'examples' installed successfully" in result.output + assert listdir(packages_dir / "examples/Alhambra-II") + assert "tools-oss-cad-suite" not in listdir(packages_dir) + + # -- Install the reset of the packages. + # -- Both 'examples' and 'tools-oss-cad-suite' should exist, and + # -- maybe others, depending on the platform. + result = apio_runner.invoke(apio_packages, ["--install"]) + apio_runner.assert_ok(result) + assert "Package 'examples' installed successfully" not in result.output + assert ( + "Package 'oss-cad-suite' installed successfully" in result.output + ) + assert listdir(packages_dir / "examples/Alhambra-II") + assert listdir(packages_dir / "tools-oss-cad-suite/bin") + + # -- Delete a file from the examples package, we will use it as an + # -- indicator for the reinstallation of the package. + marker_file = packages_dir / "examples/Alhambra-II/ledon/ledon.v" + assert marker_file.is_file() + marker_file.unlink() + assert not marker_file.exists() + + # -- Install the examples packages without forcing. + # -- This should not do anything since it's considered to be installed. + result = apio_runner.invoke(apio_packages, ["--install", "examples"]) + apio_runner.assert_ok(result) + assert "was already install" in result.output + assert "Package 'examples' installed" not in result.output + assert not marker_file.exists() + + # -- Install the examples packages with forcing. + # -- This should recover the file. + result = apio_runner.invoke( + apio_packages, ["--install", "--force", "examples"] + ) + apio_runner.assert_ok(result) + assert "Package 'examples' installed" in result.output + assert marker_file.is_file() + + # -- Try to uninstall the 'examples' package without user approval. + # -- should exit with an error message. + assert "examples" in listdir(packages_dir) + result = apio_runner.invoke( + apio_packages, ["--uninstall", "examples"], input="n" + ) + assert result.exit_code == 1 + assert "User said no" in result.output + assert "examples" in listdir(packages_dir) + assert "tools-oss-cad-suite" in listdir(packages_dir) + + # -- Uninstall the examples package. It should delete the exemples + # -- package and will live the rest. + assert "examples" in listdir(packages_dir) + result = apio_runner.invoke( + apio_packages, ["--uninstall", "examples"], input="y" + ) + apio_runner.assert_ok(result) + assert "examples" not in listdir(packages_dir) + assert "tools-oss-cad-suite" in listdir(packages_dir) + + # -- Uninstall all packages. This should uninstall also the + # -- oss-cad-suite package. + result = apio_runner.invoke(apio_packages, ["--uninstall", "--sayyes"]) + apio_runner.assert_ok(result) + assert "examples" not in listdir(packages_dir) + assert "tools-oss-cad-suite" not in listdir(packages_dir) diff --git a/test/integration/test_projects.py b/test/integration/test_projects.py new file mode 100644 index 00000000..f7320a5e --- /dev/null +++ b/test/integration/test_projects.py @@ -0,0 +1,136 @@ +""" + Test different "apio" commands +""" + +from os import listdir, chdir +from os.path import getsize +from pathlib import Path +from test.conftest import ApioRunner +import pytest + +# -- Entry point for apio commands. +from apio.commands.clean import cli as apio_clean +from apio.commands.graph import cli as apio_graph +from apio.commands.test import cli as apio_test +from apio.commands.report import cli as apio_report +from apio.commands.lint import cli as apio_lint +from apio.commands.build import cli as apio_build +from apio.commands.packages import cli as apio_packages +from apio.commands.examples import cli as apio_examples + + +# R0801: Similar lines in 2 files +# pylint: disable=R0801 +def _test_project( + apio_runner: ApioRunner, + *, + example: str, + testbench: str, + binary: str, + report_item: str, +): + """A common project integration test. Invoked per each tested + architecture. + """ + + # -- If the option 'offline' is passed, the test is skip + # -- (This test is slow and requires internet connectivity) + if apio_runner.offline_flag: + pytest.skip("requires internet connection") + + with apio_runner.in_disposable_temp_dir(): + + # -- Config the apio test environment. + proj_dir, _, packages_dir = apio_runner.setup_env() + + # -- Create and change to project dir. + proj_dir.mkdir(exist_ok=False) + chdir(proj_dir) + + # -- Install all packages + result = apio_runner.invoke(apio_packages, ["--install", "--verbose"]) + apio_runner.assert_ok(result) + assert "'examples' installed successfully" in result.output + assert "'oss-cad-suite' installed successfully" in result.output + assert listdir(packages_dir / "examples") + assert listdir(packages_dir / "tools-oss-cad-suite") + + # -- The current proj directory should be still empty + assert not listdir(".") + + # -- Fetch example files to current directory + result = apio_runner.invoke( + apio_examples, + ["--fetch-files", example], + ) + apio_runner.assert_ok(result) + assert f"Copying {example} example files" in result.output + assert "have been successfully created!" in result.output + assert getsize("apio.ini") + + # -- Remember the list of project files. + project_files = listdir(".") + + # -- Build the project. + result = apio_runner.invoke(apio_build) + apio_runner.assert_ok(result) + assert "SUCCESS" in result.output + assert getsize(f"_build/{binary}") + + # -- Lint + result = apio_runner.invoke(apio_lint) + apio_runner.assert_ok(result) + assert "SUCCESS" in result.output + assert getsize("_build/hardware.vlt") + + # -- Test + result = apio_runner.invoke(apio_test) + apio_runner.assert_ok(result) + assert "SUCCESS" in result.output + assert getsize(f"_build/{testbench}.out") + assert getsize(f"_build/{testbench}.vcd") + + # -- Report + result = apio_runner.invoke(apio_report) + apio_runner.assert_ok(result) + assert "SUCCESS" in result.output + assert report_item in result.output + assert getsize("_build/hardware.pnr") + + # -- Graph svg + result = apio_runner.invoke(apio_graph) + apio_runner.assert_ok(result) + assert "SUCCESS" in result.output + assert getsize("_build/hardware.dot") + assert getsize("_build/hardware.svg") + + # -- Clean + result = apio_runner.invoke(apio_clean) + apio_runner.assert_ok(result) + assert "SUCCESS" in result.output + assert not Path("_build").exists() + + # -- Check that we have exactly the original project files, + assert set(listdir(".")) == set(project_files) + + +def test_project_ice40(apio_runner: ApioRunner): + """Tests building and testing an ice40 project.""" + _test_project( + apio_runner, + example="Alhambra-II/ledon", + testbench="ledon_tb", + binary="hardware.bin", + report_item="ICESTORM_LC:", + ) + + +def test_project_ecp5(apio_runner: ApioRunner): + """Tests building and testing an ecp5 project.""" + _test_project( + apio_runner, + example="ColorLight-5A-75B-V8/Ledon", + testbench="ledon_tb", + binary="hardware.bit", + report_item="ALU54B:", + ) diff --git a/test/integration/test_utilities.py b/test/integration/test_utilities.py new file mode 100644 index 00000000..a9f559be --- /dev/null +++ b/test/integration/test_utilities.py @@ -0,0 +1,58 @@ +""" + Test different "apio" commands +""" + +from os import listdir, chdir +from test.conftest import ApioRunner +import pytest + + +# -- Entry point for apio commands. +from apio.commands.raw import cli as apio_raw +from apio.commands.upgrade import cli as apio_upgrade +from apio.commands.system import cli as apio_system +from apio.commands.packages import cli as apio_packages + + +# R0801: Similar lines in 2 files +# pylint: disable=R0801 +def test_utilities(apio_runner: ApioRunner): + """Tests apio utility commands.""" + + # -- If the option 'offline' is passed, the test is skip + # -- (This test is slow and requires internet connectivity) + if apio_runner.offline_flag: + pytest.skip("requires internet connection") + + with apio_runner.in_disposable_temp_dir(): + + # -- Config the apio test environment. + proj_dir, _, packages_dir = apio_runner.setup_env() + + # -- Create and change to project dir. + proj_dir.mkdir(exist_ok=False) + chdir(proj_dir) + + # -- Install all packages + result = apio_runner.invoke(apio_packages, ["--install", "--verbose"]) + apio_runner.assert_ok(result) + assert "'examples' installed successfully" in result.output + assert "'oss-cad-suite' installed successfully" in result.output + assert listdir(packages_dir / "examples") + assert listdir(packages_dir / "tools-oss-cad-suite") + + # -- Run 'apio upgrade' + result = apio_runner.invoke(apio_upgrade) + apio_runner.assert_ok(result) + assert "Lastest Apio stable version" in result.output + + # -- Run 'apio system --info' + result = apio_runner.invoke(apio_system, ["--info"]) + apio_runner.assert_ok(result) + assert "Apio version" in result.output + + # -- Run 'apio raw --env "nextpnr-ice40 --help" + result = apio_runner.invoke( + apio_raw, ["--env", "nextpnr-ice40 --help"], input="exit" + ) + apio_runner.assert_ok(result) diff --git a/test/test_apio.py b/test/test_apio.py index 9691de68..67e0c917 100644 --- a/test/test_apio.py +++ b/test/test_apio.py @@ -2,6 +2,9 @@ Test for command apio """ +from test.conftest import ApioRunner + + # ----------------------------------------------------------------------- # -- RUN manually: # -- pytest -v test/test_apio.py @@ -14,34 +17,31 @@ # -- pytest -v -s test/test_apio.py::test_apio # ------------------------------------------------------------------------ -from click.testing import CliRunner # -- Import the cli entry point: apio/__main__.py from apio.__main__ import cli as cmd_apio -def test_apio( - click_cmd_runner: CliRunner, assert_apio_cmd_ok, setup_apio_test_env -): +def test_apio(apio_runner: ApioRunner): """Test command "apio" without arguments $ apio Usage: apio [OPTIONS] COMMAND [ARGS]... [...] """ - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the apio test environment - setup_apio_test_env() + apio_runner.setup_env() # -- Invoke the apio command - result = click_cmd_runner.invoke(cmd_apio) + result = apio_runner.invoke(cmd_apio) # -- Check that everything is ok - assert_apio_cmd_ok(result) + apio_runner.assert_ok(result) -def test_apio_wrong_command(click_cmd_runner: CliRunner, setup_apio_test_env): +def test_apio_wrong_command(apio_runner: ApioRunner): """Test apio command with an invalid command $ apio wrong Usage: apio [OPTIONS] COMMAND [ARGS]... @@ -50,13 +50,13 @@ def test_apio_wrong_command(click_cmd_runner: CliRunner, setup_apio_test_env): Error: No such command 'wrong'. """ - with click_cmd_runner.isolated_filesystem(): + with apio_runner.in_disposable_temp_dir(): # -- Config the environment - setup_apio_test_env() + apio_runner.setup_env() # -- Execute "apio mmissing_command" - result = click_cmd_runner.invoke(cmd_apio, ["wrong_command"]) + result = apio_runner.invoke(cmd_apio, ["wrong_command"]) # -- Check the error code assert result.exit_code == 2, result.output diff --git a/test/test_end_to_end.py b/test/test_end_to_end.py deleted file mode 100644 index f4ad9c2b..00000000 --- a/test/test_end_to_end.py +++ /dev/null @@ -1,270 +0,0 @@ -""" - Test different "apio" commands -""" - -import pathlib - -import pytest - -# -- Entry point for apio commands. -from apio.commands.packages import cli as cmd_packages -from apio.commands.create import cli as cmd_create -from apio.commands.upload import cli as cmd_upload -from apio.commands.examples import cli as cmd_examples - - -def validate_files_leds(folder): - """Check that the ledon.v file is inside the given folder""" - - # -- File to check - leds = folder / pathlib.Path("ledon.v") - - # -- The file should exists and have a size greather than 0 - assert leds.exists() and leds.stat().st_size > 0 # getsize(leds) > 0 - - -def validate_dir_leds(folder=""): - """Check that the leds folder has been created in the - dir directory - """ - - # -- Get the leds path - leds_dir = folder / pathlib.Path("Alhambra-II/ledon") - - # -- Calculate the numer of files in the leds folder - nfiles = len(list(leds_dir.glob("*"))) - - # -- The folder should exist, and it should - # -- containe more than 0 files inside - assert leds_dir.is_dir() and nfiles > 0 - - -def test_end_to_end1( - click_cmd_runner, assert_apio_cmd_ok, setup_apio_test_env, offline_flag -): - """Test the installation of the examples package""" - - # -- If the option 'offline' is passed, the test is skip - # -- (These tests require internet) - if offline_flag: - pytest.skip("requires internet connection") - - with click_cmd_runner.isolated_filesystem(): - - # -- Config the apio test environment - setup_apio_test_env() - - # -- Execute "apio packages --uninstall examples" - result = click_cmd_runner.invoke( - cmd_packages, ["--uninstall", "examples"], input="y" - ) - assert "Do you want to uninstall 1 package?" in result.output - assert "Package 'examples' was not installed" in result.output - - # -- Execute "apio packages --install examples@X" - result = click_cmd_runner.invoke( - cmd_packages, ["--install", "examples@X"] - ) - assert "Error: package not found" in result.output - - # -- Execute "apio packages --install examples@0.0.34" - result = click_cmd_runner.invoke( - cmd_packages, ["--install", "examples@0.0.34"] - ) - assert_apio_cmd_ok(result) - assert "Installing package 'examples@" in result.output - assert "Download" in result.output - assert "Package 'examples' installed successfully" in result.output - - # -- Execute "apio packages --install examples" - result = click_cmd_runner.invoke( - cmd_packages, ["--install", "examples"] - ) - assert_apio_cmd_ok(result) - assert "Installing package 'examples'" in result.output - assert "Download" in result.output - assert "Package 'examples' installed successfully" in result.output - - # -- Execute "apio packages --install examples" again - result = click_cmd_runner.invoke( - cmd_packages, ["--install", "examples"] - ) - assert_apio_cmd_ok(result) - assert "Installing package 'examples'" in result.output - assert "was already installed" in result.output - - # -- Execute "apio packages --install examples --force" - result = click_cmd_runner.invoke( - cmd_packages, - [ - "--install", - "examples", - "--force", - ], - ) - assert_apio_cmd_ok(result) - assert "Installing package 'examples'" in result.output - assert "Download" in result.output - assert "Package 'examples' installed successfully" in result.output - - # -- Execute "apio packages --list" - result = click_cmd_runner.invoke(cmd_packages, ["--list"]) - assert result.exit_code == 0, result.output - assert "No errors" in result.output - assert "Installed packages:" in result.output - assert "examples" in result.output - - -def test_end_to_end2( - click_cmd_runner, assert_apio_cmd_ok, setup_apio_test_env, offline_flag -): - """Test more 'apio examples' commands""" - - # -- If the option 'offline' is passed, the test is skip - # -- (These tests require internet) - if offline_flag: - pytest.skip("requires internet connection") - - with click_cmd_runner.isolated_filesystem(): - - # -- Config the apio test environment - setup_apio_test_env() - - # -- Execute "apio create --board alhambra-ii" - result = click_cmd_runner.invoke( - cmd_create, ["--board", "alhambra-ii"] - ) - assert_apio_cmd_ok(result) - assert "Creating apio.ini file ..." in result.output - assert "was created successfully" in result.output - - # -- Execute "apio upload" - result = click_cmd_runner.invoke(cmd_upload) - assert result.exit_code == 1, result.output - assert "package 'oss-cad-suite' is not installed" in result.output - - # -- Execute "apio packages --install examples" - result = click_cmd_runner.invoke( - cmd_packages, ["--install", "examples"] - ) - assert_apio_cmd_ok(result) - assert "Installing package 'examples'" in result.output - assert "Download" in result.output - assert "Package 'examples' installed successfully" in result.output - - # -- Execute "apio examples --list" - result = click_cmd_runner.invoke(cmd_examples, ["--list"]) - assert_apio_cmd_ok(result) - assert "leds" in result.output - assert "icezum" in result.output - - # -- Execute "apio examples --fetch-files missing_example" - result = click_cmd_runner.invoke( - cmd_examples, ["--fetch-files", "missing_example"] - ) - assert result.exit_code == 1, result.output - assert "Warning: this example does not exist" in result.output - - # -- Execute "apio examples --fetch-files Alhambra-II/ledon" - result = click_cmd_runner.invoke( - cmd_examples, ["--fetch-files", "Alhambra-II/ledon"] - ) - assert_apio_cmd_ok(result) - assert "Copying Alhambra-II/ledon example files ..." in result.output - assert "have been successfully created!" in result.output - validate_files_leds(pathlib.Path()) - - # -- Execute "apio examples --fetch-dir Alhambra-II/ledon" - result = click_cmd_runner.invoke( - cmd_examples, ["--fetch-dir", "Alhambra-II/ledon"] - ) - assert_apio_cmd_ok(result) - assert "Creating Alhambra-II/ledon directory ..." in result.output - assert "has been successfully created" in result.output - validate_dir_leds() - - # -- Execute "apio examples --fetch-dir Alhambra-II/ledon" - result = click_cmd_runner.invoke( - cmd_examples, ["--fetch-dir", "Alhambra-II/ledon"], input="y" - ) - assert_apio_cmd_ok(result) - assert ( - "Warning: Alhambra-II/ledon directory already exists" - in result.output - ) - assert "Do you want to replace it?" in result.output - assert "Creating Alhambra-II/ledon directory ..." in result.output - assert "has been successfully created" in result.output - validate_dir_leds() - - -def test_end_to_end3( - click_cmd_runner, assert_apio_cmd_ok, setup_apio_test_env, offline_flag -): - """Test more 'apio examples' commands""" - - # -- If the option 'offline' is passed, the test is skip - # -- (These tests require internet) - if offline_flag: - pytest.skip("requires internet connection") - - with click_cmd_runner.isolated_filesystem(): - - # -- Config the apio test environment - setup_apio_test_env() - - # -- Execute "apio packages --install examples" - result = click_cmd_runner.invoke( - cmd_packages, ["--install", "examples"] - ) - assert_apio_cmd_ok(result) - assert "Installing package 'examples'" in result.output - assert "Download" in result.output - assert "Package 'examples' installed successfully" in result.output - - # ------------------------------------------ - # -- Check the --project-dir parameter - # ------------------------------------------ - # -- Create a tmp dir - p = pathlib.Path("tmp/") - p.mkdir(parents=True, exist_ok=True) - - # -- Execute "apio examples --fetch-files Alhambra-II/ledon - # -- --project-dir=tmp" - result = click_cmd_runner.invoke( - cmd_examples, - ["--fetch-files", "Alhambra-II/ledon", "--project-dir=tmp"], - ) - assert_apio_cmd_ok(result) - assert "Copying Alhambra-II/ledon example files ..." in result.output - assert "have been successfully created!" in result.output - - # -- Check the files in the tmp folder - validate_files_leds(p) - - # -- Execute - # -- "apio examples --fetch-dir Alhambra-II/ledon --project-dir=tmp" - result = click_cmd_runner.invoke( - cmd_examples, - ["--fetch-dir", "Alhambra-II/ledon", "--project-dir=tmp"], - ) - assert_apio_cmd_ok(result) - assert "Creating Alhambra-II/ledon directory ..." in result.output - assert "has been successfully created" in result.output - validate_dir_leds("tmp") - - # -- Execute "apio packages --uninstall examples" - result = click_cmd_runner.invoke( - cmd_packages, ["--uninstall", "examples"], input="n" - ) - assert result.exit_code != 0, result.output - assert "User said no" in result.output - - # -- Execute "apio packages --uninstall examples" - result = click_cmd_runner.invoke( - cmd_packages, ["--uninstall", "examples"], input="y" - ) - assert_apio_cmd_ok(result) - assert "Uninstalling package 'examples'" in result.output - assert "Do you want to uninstall 1 package?" in result.output - assert "Package 'examples' uninstalled successfuly" in result.output diff --git a/tox.ini b/tox.ini index acdfa137..13689452 100644 --- a/tox.ini +++ b/tox.ini @@ -73,7 +73,7 @@ deps = pytest-cov==5.0.0 commands = - python -m pytest --cov-report html --cov apio test + python -m pytest --cov-report html --cov apio test {posargs} # ----------------------------------------------------