diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..2994eba3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,19 @@ +# Basic .gitattributes for a python repo. + +# Source files +# ============ +*.py text diff=python eol=lf whitespace=trailing-space,space-before-tab,tab-in-indent,tabwidth=4 + +# Binary files +# ============ +*.db binary +*.p binary +*.pkl binary +*.pickle binary +*.pyc binary export-ignore +*.pyo binary export-ignore +*.pyd binary + +# Jupyter notebook +*.ipynb text eol=lf + diff --git a/.gitignore b/.gitignore index aa32746c..91d627a1 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ __pycache__/ .doit.db docs/_build +.cache + diff --git a/chipflow_lib/config_models.py b/chipflow_lib/config_models.py index eb8697ad..e87d9c2e 100644 --- a/chipflow_lib/config_models.py +++ b/chipflow_lib/config_models.py @@ -1,9 +1,8 @@ # SPDX-License-Identifier: BSD-2-Clause -import enum import re -from typing import Dict, List, Optional, Union, Literal, Any +from typing import Dict, Optional, Literal, Any -from pydantic import BaseModel, Field, model_validator, ValidationInfo, field_validator +from pydantic import BaseModel, model_validator, ValidationInfo, field_validator from .platforms.utils import Process @@ -19,7 +18,7 @@ def validate_loc_format(self): if not re.match(r"^[NSWE]?[0-9]+$", self.loc): raise ValueError(f"Invalid location format: {self.loc}, expected format: [NSWE]?[0-9]+") return self - + @classmethod def validate_pad_dict(cls, v: dict, info: ValidationInfo): """Custom validation for pad dicts from TOML that may not have all fields.""" @@ -28,11 +27,11 @@ def validate_pad_dict(cls, v: dict, info: ValidationInfo): if 'loc' in v and 'type' not in v: if info.field_name == 'power': v['type'] = 'power' - + # Map legacy 'clk' type to 'clock' to match our enum if 'type' in v and v['type'] == 'clk': v['type'] = 'clock' - + return v return v @@ -44,7 +43,7 @@ class SiliconConfig(BaseModel): pads: Dict[str, PadConfig] = {} power: Dict[str, PadConfig] = {} debug: Optional[Dict[str, bool]] = None - + @field_validator('pads', 'power', mode='before') @classmethod def validate_pad_dicts(cls, v, info: ValidationInfo): diff --git a/chipflow_lib/pin_lock.py b/chipflow_lib/pin_lock.py index 2b46924c..30ae885a 100644 --- a/chipflow_lib/pin_lock.py +++ b/chipflow_lib/pin_lock.py @@ -8,7 +8,7 @@ from chipflow_lib import _parse_config, ChipFlowError from chipflow_lib.platforms import PACKAGE_DEFINITIONS, PIN_ANNOTATION_SCHEMA, top_interfaces -from chipflow_lib.platforms.utils import LockFile, Package, PortMap, Port, Process +from chipflow_lib.platforms.utils import LockFile, Package, PortMap, Port from chipflow_lib.config_models import Config # logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) @@ -79,11 +79,10 @@ def allocate_pins(name: str, member: Dict[str, Any], pins: List[str], port_name: def lock_pins() -> None: # Get the config as dict for backward compatibility with top_interfaces config_dict = _parse_config() - + # Parse with Pydantic for type checking and strong typing - from chipflow_lib.config_models import Config config_model = Config.model_validate(config_dict) - + used_pins = set() oldlock = None diff --git a/chipflow_lib/platforms/silicon.py b/chipflow_lib/platforms/silicon.py index 04826417..a6aa0874 100644 --- a/chipflow_lib/platforms/silicon.py +++ b/chipflow_lib/platforms/silicon.py @@ -11,7 +11,7 @@ from amaranth.lib import wiring, io from amaranth.lib.cdc import FFSynchronizer -from amaranth.lib.wiring import Component, In, PureInterface, flipped, connect +from amaranth.lib.wiring import Component, In, PureInterface from amaranth.back import rtlil from amaranth.hdl import Fragment @@ -74,12 +74,12 @@ def __init__(self, self._invert = invert self._options = port.options self._pins = port.pins - + # Initialize signal attributes to None self._i = None self._o = None self._oe = None - + # Create signals based on direction if self._direction in (io.Direction.Input, io.Direction.Bidir): self._i = Signal(port.width, name=f"{component}_{name}__i") diff --git a/chipflow_lib/steps/silicon.py b/chipflow_lib/steps/silicon.py index 4ef6f501..5df149db 100644 --- a/chipflow_lib/steps/silicon.py +++ b/chipflow_lib/steps/silicon.py @@ -149,7 +149,7 @@ def submit(self, rtlil_path, *, dry_run=False): logger.debug(f"padname={padname}, port={port}, loc={port.pins[0]}, " f"dir={port.direction}, width={width}") pads[padname] = {'loc': port.pins[0], 'type': port.direction.value} - + # Use the Pydantic models to access configuration data silicon_model = self.config_model.chipflow.silicon config = { diff --git a/pdm.lock b/pdm.lock index 9ccc869c..d6794a84 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "doc", "lint", "test"] strategy = ["direct_minimal_versions", "inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:79834ab8abd925af18de424d2824347d92076f8ce026b7d9caf14765e155e774" +content_hash = "sha256:8f99b01769cc1499d15a515a9f224863fb66a6e80b6edb59351466cbb4205fa7" [[metadata.targets]] requires_python = "~=3.10" @@ -54,10 +54,10 @@ files = [ [[package]] name = "amaranth-boards" -version = "0.1.dev256" +version = "0.1.dev257" requires_python = "~=3.9" git = "https://github.com/amaranth-lang/amaranth-boards" -revision = "6e01882eefd62cf19f5740406144632fe2d21947" +revision = "ba3b403a3391b5e306ba1ced61c3368839af61a6" summary = "Board and connector definitions for Amaranth HDL" groups = ["default"] dependencies = [ @@ -116,13 +116,13 @@ files = [ [[package]] name = "attrs" -version = "25.1.0" +version = "25.3.0" requires_python = ">=3.8" summary = "Classes Without Boilerplate" -groups = ["default", "lint", "test"] +groups = ["default", "test"] files = [ - {file = "attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a"}, - {file = "attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e"}, + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [[package]] @@ -206,7 +206,7 @@ name = "colorama" version = "0.4.6" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" summary = "Cross-platform colored terminal text." -groups = ["default", "doc", "lint", "test"] +groups = ["default", "doc", "test"] marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, @@ -215,131 +215,131 @@ files = [ [[package]] name = "coverage" -version = "7.6.12" +version = "7.7.0" requires_python = ">=3.9" summary = "Code coverage measurement for Python" -groups = ["lint", "test"] +groups = ["test"] files = [ - {file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"}, - {file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"}, - {file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"}, - {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"}, - {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"}, - {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"}, - {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"}, - {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"}, - {file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"}, - {file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"}, - {file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"}, - {file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"}, - {file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"}, - {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"}, - {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"}, - {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"}, - {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"}, - {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"}, - {file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"}, - {file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"}, - {file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"}, - {file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"}, - {file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"}, - {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"}, - {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"}, - {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"}, - {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"}, - {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"}, - {file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"}, - {file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"}, - {file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"}, - {file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"}, - {file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"}, - {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"}, - {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"}, - {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"}, - {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"}, - {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"}, - {file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"}, - {file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"}, - {file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"}, - {file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"}, - {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"}, - {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"}, - {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"}, - {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"}, - {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"}, - {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"}, - {file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"}, - {file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"}, - {file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"}, - {file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"}, - {file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"}, + {file = "coverage-7.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a538a23119d1e2e2ce077e902d02ea3d8e0641786ef6e0faf11ce82324743944"}, + {file = "coverage-7.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1586ad158523f4133499a4f322b230e2cfef9cc724820dbd58595a5a236186f4"}, + {file = "coverage-7.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b6c96d69928a3a6767fab8dc1ce8a02cf0156836ccb1e820c7f45a423570d98"}, + {file = "coverage-7.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f18d47641282664276977c604b5a261e51fefc2980f5271d547d706b06a837f"}, + {file = "coverage-7.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a1e18a85bd066c7c556d85277a7adf4651f259b2579113844835ba1a74aafd"}, + {file = "coverage-7.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:70f0925c4e2bfc965369f417e7cc72538fd1ba91639cf1e4ef4b1a6b50439b3b"}, + {file = "coverage-7.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b0fac2088ec4aaeb5468b814bd3ff5e5978364bfbce5e567c44c9e2854469f6c"}, + {file = "coverage-7.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3e212a894d8ae07fde2ca8b43d666a6d49bbbddb10da0f6a74ca7bd31f20054"}, + {file = "coverage-7.7.0-cp310-cp310-win32.whl", hash = "sha256:f32b165bf6dfea0846a9c9c38b7e1d68f313956d60a15cde5d1709fddcaf3bee"}, + {file = "coverage-7.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:a2454b12a3f12cc4698f3508912e6225ec63682e2ca5a96f80a2b93cef9e63f3"}, + {file = "coverage-7.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a0a207c87a9f743c8072d059b4711f8d13c456eb42dac778a7d2e5d4f3c253a7"}, + {file = "coverage-7.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d673e3add00048215c2cc507f1228a7523fd8bf34f279ac98334c9b07bd2656"}, + {file = "coverage-7.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f81fe93dc1b8e5673f33443c0786c14b77e36f1025973b85e07c70353e46882b"}, + {file = "coverage-7.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8c7524779003d59948c51b4fcbf1ca4e27c26a7d75984f63488f3625c328b9b"}, + {file = "coverage-7.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c124025430249118d018dcedc8b7426f39373527c845093132196f2a483b6dd"}, + {file = "coverage-7.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e7f559c36d5cdc448ee13e7e56ed7b6b5d44a40a511d584d388a0f5d940977ba"}, + {file = "coverage-7.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:37cbc7b0d93dfd133e33c7ec01123fbb90401dce174c3b6661d8d36fb1e30608"}, + {file = "coverage-7.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7d2a65876274acf544703e943c010b60bd79404e3623a1e5d52b64a6e2728de5"}, + {file = "coverage-7.7.0-cp311-cp311-win32.whl", hash = "sha256:f5a2f71d6a91238e7628f23538c26aa464d390cbdedf12ee2a7a0fb92a24482a"}, + {file = "coverage-7.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:ae8006772c6b0fa53c33747913473e064985dac4d65f77fd2fdc6474e7cd54e4"}, + {file = "coverage-7.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:056d3017ed67e7ddf266e6f57378ece543755a4c9231e997789ab3bd11392c94"}, + {file = "coverage-7.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33c1394d8407e2771547583b66a85d07ed441ff8fae5a4adb4237ad39ece60db"}, + {file = "coverage-7.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fbb7a0c3c21908520149d7751cf5b74eb9b38b54d62997b1e9b3ac19a8ee2fe"}, + {file = "coverage-7.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb356e7ae7c2da13f404bf8f75be90f743c6df8d4607022e759f5d7d89fe83f8"}, + {file = "coverage-7.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce730d484038e97f27ea2dbe5d392ec5c2261f28c319a3bb266f6b213650135"}, + {file = "coverage-7.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa4dff57fc21a575672176d5ab0ef15a927199e775c5e8a3d75162ab2b0c7705"}, + {file = "coverage-7.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b667b91f4f714b17af2a18e220015c941d1cf8b07c17f2160033dbe1e64149f0"}, + {file = "coverage-7.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:693d921621a0c8043bfdc61f7d4df5ea6d22165fe8b807cac21eb80dd94e4bbd"}, + {file = "coverage-7.7.0-cp312-cp312-win32.whl", hash = "sha256:52fc89602cde411a4196c8c6894afb384f2125f34c031774f82a4f2608c59d7d"}, + {file = "coverage-7.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ce8cf59e09d31a4915ff4c3b94c6514af4c84b22c4cc8ad7c3c546a86150a92"}, + {file = "coverage-7.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4545485fef7a8a2d8f30e6f79ce719eb154aab7e44217eb444c1d38239af2072"}, + {file = "coverage-7.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1393e5aa9441dafb0162c36c8506c648b89aea9565b31f6bfa351e66c11bcd82"}, + {file = "coverage-7.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:316f29cc3392fa3912493ee4c83afa4a0e2db04ff69600711f8c03997c39baaa"}, + {file = "coverage-7.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1ffde1d6bc2a92f9c9207d1ad808550873748ac2d4d923c815b866baa343b3f"}, + {file = "coverage-7.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:416e2a8845eaff288f97eaf76ab40367deafb9073ffc47bf2a583f26b05e5265"}, + {file = "coverage-7.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5efdeff5f353ed3352c04e6b318ab05c6ce9249c25ed3c2090c6e9cadda1e3b2"}, + {file = "coverage-7.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:57f3bd0d29bf2bd9325c0ff9cc532a175110c4bf8f412c05b2405fd35745266d"}, + {file = "coverage-7.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ab7090f04b12dc6469882ce81244572779d3a4b67eea1c96fb9ecc8c607ef39"}, + {file = "coverage-7.7.0-cp313-cp313-win32.whl", hash = "sha256:180e3fc68ee4dc5af8b33b6ca4e3bb8aa1abe25eedcb958ba5cff7123071af68"}, + {file = "coverage-7.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:55143aa13c49491f5606f05b49ed88663446dce3a4d3c5d77baa4e36a16d3573"}, + {file = "coverage-7.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:cc41374d2f27d81d6558f8a24e5c114580ffefc197fd43eabd7058182f743322"}, + {file = "coverage-7.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:89078312f06237417adda7c021c33f80f7a6d2db8572a5f6c330d89b080061ce"}, + {file = "coverage-7.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b2f144444879363ea8834cd7b6869d79ac796cb8f864b0cfdde50296cd95816"}, + {file = "coverage-7.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:60e6347d1ed882b1159ffea172cb8466ee46c665af4ca397edbf10ff53e9ffaf"}, + {file = "coverage-7.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb203c0afffaf1a8f5b9659a013f8f16a1b2cad3a80a8733ceedc968c0cf4c57"}, + {file = "coverage-7.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ad0edaa97cb983d9f2ff48cadddc3e1fb09f24aa558abeb4dc9a0dbacd12cbb4"}, + {file = "coverage-7.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c5f8a5364fc37b2f172c26a038bc7ec4885f429de4a05fc10fdcb53fb5834c5c"}, + {file = "coverage-7.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4e09534037933bf6eb31d804e72c52ec23219b32c1730f9152feabbd7499463"}, + {file = "coverage-7.7.0-cp313-cp313t-win32.whl", hash = "sha256:1b336d06af14f8da5b1f391e8dec03634daf54dfcb4d1c4fb6d04c09d83cef90"}, + {file = "coverage-7.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b54a1ee4c6f1905a436cbaa04b26626d27925a41cbc3a337e2d3ff7038187f07"}, + {file = "coverage-7.7.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:3b0e6e54591ae0d7427def8a4d40fca99df6b899d10354bab73cd5609807261c"}, + {file = "coverage-7.7.0-py3-none-any.whl", hash = "sha256:708f0a1105ef2b11c79ed54ed31f17e6325ac936501fc373f24be3e6a578146a"}, + {file = "coverage-7.7.0.tar.gz", hash = "sha256:cd879d4646055a573775a1cec863d00c9ff8c55860f8b17f6d8eee9140c06166"}, ] [[package]] name = "coverage" -version = "7.6.12" +version = "7.7.0" extras = ["toml"] requires_python = ">=3.9" summary = "Code coverage measurement for Python" -groups = ["lint", "test"] +groups = ["test"] dependencies = [ - "coverage==7.6.12", + "coverage==7.7.0", "tomli; python_full_version <= \"3.11.0a6\"", ] files = [ - {file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"}, - {file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"}, - {file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"}, - {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"}, - {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"}, - {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"}, - {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"}, - {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"}, - {file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"}, - {file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"}, - {file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"}, - {file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"}, - {file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"}, - {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"}, - {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"}, - {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"}, - {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"}, - {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"}, - {file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"}, - {file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"}, - {file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"}, - {file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"}, - {file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"}, - {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"}, - {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"}, - {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"}, - {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"}, - {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"}, - {file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"}, - {file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"}, - {file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"}, - {file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"}, - {file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"}, - {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"}, - {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"}, - {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"}, - {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"}, - {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"}, - {file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"}, - {file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"}, - {file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"}, - {file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"}, - {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"}, - {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"}, - {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"}, - {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"}, - {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"}, - {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"}, - {file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"}, - {file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"}, - {file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"}, - {file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"}, - {file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"}, + {file = "coverage-7.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a538a23119d1e2e2ce077e902d02ea3d8e0641786ef6e0faf11ce82324743944"}, + {file = "coverage-7.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1586ad158523f4133499a4f322b230e2cfef9cc724820dbd58595a5a236186f4"}, + {file = "coverage-7.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b6c96d69928a3a6767fab8dc1ce8a02cf0156836ccb1e820c7f45a423570d98"}, + {file = "coverage-7.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f18d47641282664276977c604b5a261e51fefc2980f5271d547d706b06a837f"}, + {file = "coverage-7.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a1e18a85bd066c7c556d85277a7adf4651f259b2579113844835ba1a74aafd"}, + {file = "coverage-7.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:70f0925c4e2bfc965369f417e7cc72538fd1ba91639cf1e4ef4b1a6b50439b3b"}, + {file = "coverage-7.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b0fac2088ec4aaeb5468b814bd3ff5e5978364bfbce5e567c44c9e2854469f6c"}, + {file = "coverage-7.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3e212a894d8ae07fde2ca8b43d666a6d49bbbddb10da0f6a74ca7bd31f20054"}, + {file = "coverage-7.7.0-cp310-cp310-win32.whl", hash = "sha256:f32b165bf6dfea0846a9c9c38b7e1d68f313956d60a15cde5d1709fddcaf3bee"}, + {file = "coverage-7.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:a2454b12a3f12cc4698f3508912e6225ec63682e2ca5a96f80a2b93cef9e63f3"}, + {file = "coverage-7.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a0a207c87a9f743c8072d059b4711f8d13c456eb42dac778a7d2e5d4f3c253a7"}, + {file = "coverage-7.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d673e3add00048215c2cc507f1228a7523fd8bf34f279ac98334c9b07bd2656"}, + {file = "coverage-7.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f81fe93dc1b8e5673f33443c0786c14b77e36f1025973b85e07c70353e46882b"}, + {file = "coverage-7.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8c7524779003d59948c51b4fcbf1ca4e27c26a7d75984f63488f3625c328b9b"}, + {file = "coverage-7.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c124025430249118d018dcedc8b7426f39373527c845093132196f2a483b6dd"}, + {file = "coverage-7.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e7f559c36d5cdc448ee13e7e56ed7b6b5d44a40a511d584d388a0f5d940977ba"}, + {file = "coverage-7.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:37cbc7b0d93dfd133e33c7ec01123fbb90401dce174c3b6661d8d36fb1e30608"}, + {file = "coverage-7.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7d2a65876274acf544703e943c010b60bd79404e3623a1e5d52b64a6e2728de5"}, + {file = "coverage-7.7.0-cp311-cp311-win32.whl", hash = "sha256:f5a2f71d6a91238e7628f23538c26aa464d390cbdedf12ee2a7a0fb92a24482a"}, + {file = "coverage-7.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:ae8006772c6b0fa53c33747913473e064985dac4d65f77fd2fdc6474e7cd54e4"}, + {file = "coverage-7.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:056d3017ed67e7ddf266e6f57378ece543755a4c9231e997789ab3bd11392c94"}, + {file = "coverage-7.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33c1394d8407e2771547583b66a85d07ed441ff8fae5a4adb4237ad39ece60db"}, + {file = "coverage-7.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fbb7a0c3c21908520149d7751cf5b74eb9b38b54d62997b1e9b3ac19a8ee2fe"}, + {file = "coverage-7.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb356e7ae7c2da13f404bf8f75be90f743c6df8d4607022e759f5d7d89fe83f8"}, + {file = "coverage-7.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce730d484038e97f27ea2dbe5d392ec5c2261f28c319a3bb266f6b213650135"}, + {file = "coverage-7.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa4dff57fc21a575672176d5ab0ef15a927199e775c5e8a3d75162ab2b0c7705"}, + {file = "coverage-7.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b667b91f4f714b17af2a18e220015c941d1cf8b07c17f2160033dbe1e64149f0"}, + {file = "coverage-7.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:693d921621a0c8043bfdc61f7d4df5ea6d22165fe8b807cac21eb80dd94e4bbd"}, + {file = "coverage-7.7.0-cp312-cp312-win32.whl", hash = "sha256:52fc89602cde411a4196c8c6894afb384f2125f34c031774f82a4f2608c59d7d"}, + {file = "coverage-7.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ce8cf59e09d31a4915ff4c3b94c6514af4c84b22c4cc8ad7c3c546a86150a92"}, + {file = "coverage-7.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4545485fef7a8a2d8f30e6f79ce719eb154aab7e44217eb444c1d38239af2072"}, + {file = "coverage-7.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1393e5aa9441dafb0162c36c8506c648b89aea9565b31f6bfa351e66c11bcd82"}, + {file = "coverage-7.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:316f29cc3392fa3912493ee4c83afa4a0e2db04ff69600711f8c03997c39baaa"}, + {file = "coverage-7.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1ffde1d6bc2a92f9c9207d1ad808550873748ac2d4d923c815b866baa343b3f"}, + {file = "coverage-7.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:416e2a8845eaff288f97eaf76ab40367deafb9073ffc47bf2a583f26b05e5265"}, + {file = "coverage-7.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5efdeff5f353ed3352c04e6b318ab05c6ce9249c25ed3c2090c6e9cadda1e3b2"}, + {file = "coverage-7.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:57f3bd0d29bf2bd9325c0ff9cc532a175110c4bf8f412c05b2405fd35745266d"}, + {file = "coverage-7.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ab7090f04b12dc6469882ce81244572779d3a4b67eea1c96fb9ecc8c607ef39"}, + {file = "coverage-7.7.0-cp313-cp313-win32.whl", hash = "sha256:180e3fc68ee4dc5af8b33b6ca4e3bb8aa1abe25eedcb958ba5cff7123071af68"}, + {file = "coverage-7.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:55143aa13c49491f5606f05b49ed88663446dce3a4d3c5d77baa4e36a16d3573"}, + {file = "coverage-7.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:cc41374d2f27d81d6558f8a24e5c114580ffefc197fd43eabd7058182f743322"}, + {file = "coverage-7.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:89078312f06237417adda7c021c33f80f7a6d2db8572a5f6c330d89b080061ce"}, + {file = "coverage-7.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b2f144444879363ea8834cd7b6869d79ac796cb8f864b0cfdde50296cd95816"}, + {file = "coverage-7.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:60e6347d1ed882b1159ffea172cb8466ee46c665af4ca397edbf10ff53e9ffaf"}, + {file = "coverage-7.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb203c0afffaf1a8f5b9659a013f8f16a1b2cad3a80a8733ceedc968c0cf4c57"}, + {file = "coverage-7.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ad0edaa97cb983d9f2ff48cadddc3e1fb09f24aa558abeb4dc9a0dbacd12cbb4"}, + {file = "coverage-7.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c5f8a5364fc37b2f172c26a038bc7ec4885f429de4a05fc10fdcb53fb5834c5c"}, + {file = "coverage-7.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4e09534037933bf6eb31d804e72c52ec23219b32c1730f9152feabbd7499463"}, + {file = "coverage-7.7.0-cp313-cp313t-win32.whl", hash = "sha256:1b336d06af14f8da5b1f391e8dec03634daf54dfcb4d1c4fb6d04c09d83cef90"}, + {file = "coverage-7.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b54a1ee4c6f1905a436cbaa04b26626d27925a41cbc3a337e2d3ff7038187f07"}, + {file = "coverage-7.7.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:3b0e6e54591ae0d7427def8a4d40fca99df6b899d10354bab73cd5609807261c"}, + {file = "coverage-7.7.0-py3-none-any.whl", hash = "sha256:708f0a1105ef2b11c79ed54ed31f17e6325ac936501fc373f24be3e6a578146a"}, + {file = "coverage-7.7.0.tar.gz", hash = "sha256:cd879d4646055a573775a1cec863d00c9ff8c55860f8b17f6d8eee9140c06166"}, ] [[package]] @@ -373,7 +373,7 @@ name = "exceptiongroup" version = "1.2.2" requires_python = ">=3.7" summary = "Backport of PEP 654 (exception groups)" -groups = ["lint", "test"] +groups = ["test"] marker = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, @@ -422,7 +422,7 @@ name = "iniconfig" version = "2.0.0" requires_python = ">=3.7" summary = "brain-dead simple config-ini parsing" -groups = ["lint", "test"] +groups = ["test"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -539,7 +539,7 @@ name = "packaging" version = "24.2" requires_python = ">=3.8" summary = "Core utilities for Python packages" -groups = ["doc", "lint", "test"] +groups = ["doc", "test"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -550,7 +550,7 @@ name = "pluggy" version = "1.5.0" requires_python = ">=3.8" summary = "plugin and hook calling mechanisms for python" -groups = ["lint", "test"] +groups = ["test"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -706,7 +706,7 @@ name = "pytest" version = "7.2.0" requires_python = ">=3.7" summary = "pytest: simple powerful testing with Python" -groups = ["lint", "test"] +groups = ["test"] dependencies = [ "attrs>=19.2.0", "colorama; sys_platform == \"win32\"", @@ -727,7 +727,7 @@ name = "pytest-cov" version = "6.0.0" requires_python = ">=3.9" summary = "Pytest plugin for measuring coverage." -groups = ["lint", "test"] +groups = ["test"] dependencies = [ "coverage[toml]>=7.5", "pytest>=4.6", @@ -835,11 +835,29 @@ files = [ [[package]] name = "ruff" -version = "0.0.13" -summary = "" +version = "0.9.2" +requires_python = ">=3.7" +summary = "An extremely fast Python linter and code formatter, written in Rust." groups = ["lint"] files = [ - {file = "ruff-0.0.13.tar.gz", hash = "sha256:25c6b80752dae13454c0e369560d79c5cc85fcab0e09200a565c748b91c910f2"}, + {file = "ruff-0.9.2-py3-none-linux_armv6l.whl", hash = "sha256:80605a039ba1454d002b32139e4970becf84b5fee3a3c3bf1c2af6f61a784347"}, + {file = "ruff-0.9.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b9aab82bb20afd5f596527045c01e6ae25a718ff1784cb92947bff1f83068b00"}, + {file = "ruff-0.9.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fbd337bac1cfa96be615f6efcd4bc4d077edbc127ef30e2b8ba2a27e18c054d4"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b35259b0cbf8daa22a498018e300b9bb0174c2bbb7bcba593935158a78054d"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b6a9701d1e371bf41dca22015c3f89769da7576884d2add7317ec1ec8cb9c3c"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cc53e68b3c5ae41e8faf83a3b89f4a5d7b2cb666dff4b366bb86ed2a85b481f"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8efd9da7a1ee314b910da155ca7e8953094a7c10d0c0a39bfde3fcfd2a015684"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3292c5a22ea9a5f9a185e2d131dc7f98f8534a32fb6d2ee7b9944569239c648d"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a605fdcf6e8b2d39f9436d343d1f0ff70c365a1e681546de0104bef81ce88df"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c547f7f256aa366834829a08375c297fa63386cbe5f1459efaf174086b564247"}, + {file = "ruff-0.9.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d18bba3d3353ed916e882521bc3e0af403949dbada344c20c16ea78f47af965e"}, + {file = "ruff-0.9.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b338edc4610142355ccf6b87bd356729b62bf1bc152a2fad5b0c7dc04af77bfe"}, + {file = "ruff-0.9.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:492a5e44ad9b22a0ea98cf72e40305cbdaf27fac0d927f8bc9e1df316dcc96eb"}, + {file = "ruff-0.9.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:af1e9e9fe7b1f767264d26b1075ac4ad831c7db976911fa362d09b2d0356426a"}, + {file = "ruff-0.9.2-py3-none-win32.whl", hash = "sha256:71cbe22e178c5da20e1514e1e01029c73dc09288a8028a5d3446e6bba87a5145"}, + {file = "ruff-0.9.2-py3-none-win_amd64.whl", hash = "sha256:c5e1d6abc798419cf46eed03f54f2e0c3adb1ad4b801119dedf23fcaf69b55b5"}, + {file = "ruff-0.9.2-py3-none-win_arm64.whl", hash = "sha256:a1b63fa24149918f8b37cef2ee6fff81f24f0d74b6f0bdc37bc3e1f2143e41c6"}, + {file = "ruff-0.9.2.tar.gz", hash = "sha256:b5eceb334d55fae5f316f783437392642ae18e16dcf4f1858d55d3c2a0f8f5d0"}, ] [[package]] @@ -999,7 +1017,7 @@ name = "tomli" version = "2.0.1" requires_python = ">=3.7" summary = "A lil' TOML parser" -groups = ["default", "doc", "lint", "test"] +groups = ["default", "doc", "test"] files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, diff --git a/pyproject.toml b/pyproject.toml index fbbc9f54..bee0d766 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,9 +48,15 @@ reportMissingImports = false reportUnboundVariable = false [tool.ruff] -include = ["pyproject.toml", "**/*.py", "chipflow.toml"] +include = [ + "chipflow_lib/**/*.py", + "tests/**.py", + "chipflow.toml", + "pyproject.toml" +] [tool.ruff.lint] +select = ["E4", "E7", "E9", "F", "W291", "W293"] ignore = ['F403', 'F405'] [tool.pdm.version] @@ -68,8 +74,7 @@ test-silicon.cmd = "pytest tests/test_silicon_platform.py tests/test_silicon_pla [dependency-groups] lint = [ - "ruff", - "pytest-cov", + "ruff>=0.9.2", ] test = [ "pytest>=7.2.0", diff --git a/tests/fixtures/mock_top.py b/tests/fixtures/mock_top.py index 185cae41..4339c9d7 100644 --- a/tests/fixtures/mock_top.py +++ b/tests/fixtures/mock_top.py @@ -43,5 +43,5 @@ def elaborate(self, platform): m = Module() for inpin, outpin in zip(self.test1.members, self.test2.members): m.d.comb += inpin.eq(outpin) - + return m \ No newline at end of file diff --git a/tests/test_buffers.py b/tests/test_buffers.py index 3e2816dd..7dc4fe2f 100644 --- a/tests/test_buffers.py +++ b/tests/test_buffers.py @@ -4,7 +4,7 @@ import unittest from unittest import mock -from amaranth import Module, Signal +from amaranth import Module from amaranth.lib import io # We'll need to mock SiliconPlatformPort instead of using the real one @@ -14,24 +14,24 @@ class TestBuffers(unittest.TestCase): def test_io_buffer_mocked(self, mock_ffbuffer, mock_iobuffer): """Test that IOBuffer can be imported and mocked""" from chipflow_lib.platforms.silicon import IOBuffer - + # Verify that the mock is working self.assertEqual(IOBuffer, mock_iobuffer) - + # Create a mock port port = mock.Mock() port.invert = False - + # Create a mock for the IOBuffer elaborate method module = Module() mock_iobuffer.return_value.elaborate.return_value = module - + # Create an IOBuffer instance buffer = IOBuffer(io.Direction.Input, port) - + # Elaborate the buffer result = buffer.elaborate(None) - + # Verify the result self.assertEqual(result, module) mock_iobuffer.return_value.elaborate.assert_called_once() @@ -39,24 +39,24 @@ def test_io_buffer_mocked(self, mock_ffbuffer, mock_iobuffer): def test_ff_buffer_mocked(self, mock_ffbuffer, mock_iobuffer): """Test that FFBuffer can be imported and mocked""" from chipflow_lib.platforms.silicon import FFBuffer - + # Verify that the mock is working self.assertEqual(FFBuffer, mock_ffbuffer) - + # Create a mock port port = mock.Mock() port.invert = False - + # Create a mock for the FFBuffer elaborate method module = Module() mock_ffbuffer.return_value.elaborate.return_value = module - + # Create an FFBuffer instance buffer = FFBuffer(io.Direction.Input, port, i_domain="sync", o_domain="sync") - + # Elaborate the buffer result = buffer.elaborate(None) - + # Verify the result self.assertEqual(result, module) mock_ffbuffer.return_value.elaborate.assert_called_once() \ No newline at end of file diff --git a/tests/test_cli.py b/tests/test_cli.py index 3378389c..7149bf09 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,21 +1,21 @@ # SPDX-License-Identifier: BSD-2-Clause -import os -import sys + +import pytest import unittest -import argparse from unittest import mock import logging from chipflow_lib import ChipFlowError -from chipflow_lib.cli import run, UnexpectedError +from chipflow_lib.cli import run class MockCommand: """Mock command for testing CLI""" + def build_cli_parser(self, parser): parser.add_argument("--option", help="Test option") parser.add_argument("action", choices=["valid", "error", "unexpected"]) - + def run_cli(self, args): if args.action == "error": raise ChipFlowError("Command error") @@ -39,18 +39,18 @@ def test_run_success(self, mock_get_cls, mock_pin_command, mock_parse_config): } } mock_parse_config.return_value = mock_config - + mock_pin_cmd = MockCommand() mock_pin_command.return_value = mock_pin_cmd - + mock_test_cmd = MockCommand() mock_get_cls.return_value = lambda config: mock_test_cmd - + # Capture stdout for assertion with mock.patch("sys.stdout") as mock_stdout: # Run with valid action run(["test", "valid"]) - + # No error message should be printed mock_stdout.write.assert_not_called() @@ -68,18 +68,22 @@ def test_run_command_error(self, mock_get_cls, mock_pin_command, mock_parse_conf } } mock_parse_config.return_value = mock_config - + mock_pin_cmd = MockCommand() mock_pin_command.return_value = mock_pin_cmd - + mock_test_cmd = MockCommand() mock_get_cls.return_value = lambda config: mock_test_cmd - + # Capture stdout for assertion with mock.patch("builtins.print") as mock_print: - # Run with error action - run(["test", "error"]) - + with pytest.raises(SystemExit) as systemexit: + # Run with error action + run(["test", "error"]) + + assert systemexit.type is SystemExit + assert systemexit.value.code == 1 + # Error message should be printed mock_print.assert_called_once() self.assertIn("Error while executing `test error`", mock_print.call_args[0][0]) @@ -98,18 +102,22 @@ def test_run_unexpected_error(self, mock_get_cls, mock_pin_command, mock_parse_c } } mock_parse_config.return_value = mock_config - + mock_pin_cmd = MockCommand() mock_pin_command.return_value = mock_pin_cmd - + mock_test_cmd = MockCommand() mock_get_cls.return_value = lambda config: mock_test_cmd - + # Capture stdout for assertion with mock.patch("builtins.print") as mock_print: - # Run with unexpected error action - run(["test", "unexpected"]) - + with pytest.raises(SystemExit) as systemexit: + # Run with unexpected error action + run(["test", "unexpected"]) + + assert systemexit.type is SystemExit + assert systemexit.value.code == 1 + # Error message should be printed mock_print.assert_called_once() self.assertIn("Error while executing `test unexpected`", mock_print.call_args[0][0]) @@ -128,17 +136,17 @@ def test_step_init_error(self, mock_pin_command, mock_parse_config): } } mock_parse_config.return_value = mock_config - + mock_pin_cmd = MockCommand() mock_pin_command.return_value = mock_pin_cmd - + # Make _get_cls_by_reference raise an exception during step initialization with mock.patch("chipflow_lib.cli._get_cls_by_reference") as mock_get_cls: mock_get_cls.return_value = mock.Mock(side_effect=Exception("Init error")) - + with self.assertRaises(ChipFlowError) as cm: run(["test", "valid"]) - + self.assertIn("Encountered error while initializing step", str(cm.exception)) @mock.patch("chipflow_lib.cli._parse_config") @@ -155,19 +163,19 @@ def test_build_parser_error(self, mock_get_cls, mock_pin_command, mock_parse_con } } mock_parse_config.return_value = mock_config - + # Make pin command raise an error during build_cli_parser mock_pin_cmd = mock.Mock() mock_pin_cmd.build_cli_parser.side_effect = Exception("Parser error") mock_pin_command.return_value = mock_pin_cmd - + mock_test_cmd = mock.Mock() mock_test_cmd.build_cli_parser.side_effect = Exception("Parser error") mock_get_cls.return_value = lambda config: mock_test_cmd - + with self.assertRaises(ChipFlowError) as cm: run(["pin", "lock"]) - + self.assertIn("Encountered error while building CLI argument parser", str(cm.exception)) @mock.patch("chipflow_lib.cli._parse_config") @@ -184,29 +192,29 @@ def test_verbosity_flags(self, mock_get_cls, mock_pin_command, mock_parse_config } } mock_parse_config.return_value = mock_config - + mock_pin_cmd = MockCommand() mock_pin_command.return_value = mock_pin_cmd - + mock_test_cmd = MockCommand() mock_get_cls.return_value = lambda config: mock_test_cmd - + # Save original log level original_level = logging.getLogger().level - + try: # Test with -v with mock.patch("sys.stdout"): run(["-v", "test", "valid"]) self.assertEqual(logging.getLogger().level, logging.INFO) - + # Reset log level logging.getLogger().setLevel(original_level) - + # Test with -v -v with mock.patch("sys.stdout"): run(["-v", "-v", "test", "valid"]) self.assertEqual(logging.getLogger().level, logging.DEBUG) finally: # Restore original log level - logging.getLogger().setLevel(original_level) \ No newline at end of file + logging.getLogger().setLevel(original_level) diff --git a/tests/test_init.py b/tests/test_init.py index 2fc9edb3..1fb841c4 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -4,10 +4,7 @@ import unittest import tempfile from unittest import mock -from pathlib import Path -import jsonschema -import pytest from chipflow_lib import ( ChipFlowError, @@ -23,7 +20,7 @@ def setUp(self): # Save original environment to restore later self.original_chipflow_root = os.environ.get("CHIPFLOW_ROOT") self.original_sys_path = sys.path.copy() - + # Create a temporary directory for tests self.temp_dir = tempfile.TemporaryDirectory() self.temp_path = self.temp_dir.name @@ -34,7 +31,7 @@ def tearDown(self): os.environ["CHIPFLOW_ROOT"] = self.original_chipflow_root else: os.environ.pop("CHIPFLOW_ROOT", None) - + sys.path = self.original_sys_path self.temp_dir.cleanup() @@ -53,23 +50,23 @@ def test_get_cls_by_reference_module_not_found(self): """Test _get_cls_by_reference when the module doesn't exist""" with self.assertRaises(ChipFlowError) as cm: _get_cls_by_reference("nonexistent_module:SomeClass", "test context") - + self.assertIn("Module `nonexistent_module` referenced by test context is not found", str(cm.exception)) def test_get_cls_by_reference_class_not_found(self): """Test _get_cls_by_reference when the class doesn't exist in the module""" with self.assertRaises(ChipFlowError) as cm: _get_cls_by_reference("unittest:NonExistentClass", "test context") - + self.assertIn("Module `unittest` referenced by test context does not define `NonExistentClass`", str(cm.exception)) def test_ensure_chipflow_root_already_set(self): """Test _ensure_chipflow_root when CHIPFLOW_ROOT is already set""" os.environ["CHIPFLOW_ROOT"] = "/test/path" sys.path = ["/some/other/path"] - + result = _ensure_chipflow_root() - + self.assertEqual(result, "/test/path") self.assertIn("/test/path", sys.path) @@ -77,10 +74,10 @@ def test_ensure_chipflow_root_not_set(self): """Test _ensure_chipflow_root when CHIPFLOW_ROOT is not set""" if "CHIPFLOW_ROOT" in os.environ: del os.environ["CHIPFLOW_ROOT"] - + with mock.patch("os.getcwd", return_value="/mock/cwd"): result = _ensure_chipflow_root() - + self.assertEqual(result, "/mock/cwd") self.assertEqual(os.environ["CHIPFLOW_ROOT"], "/mock/cwd") self.assertIn("/mock/cwd", sys.path) @@ -102,9 +99,9 @@ def test_parse_config_file_valid(self): config_path = os.path.join(self.temp_path, "chipflow.toml") with open(config_path, "w") as f: f.write(config_content) - + config = _parse_config_file(config_path) - + self.assertIn("chipflow", config) self.assertEqual(config["chipflow"]["project_name"], "test_project") self.assertEqual(config["chipflow"]["silicon"]["process"], "sky130") @@ -120,10 +117,10 @@ def test_parse_config_file_invalid_schema(self): config_path = os.path.join(self.temp_path, "chipflow.toml") with open(config_path, "w") as f: f.write(config_content) - + with self.assertRaises(ChipFlowError) as cm: _parse_config_file(config_path) - + self.assertIn("Syntax error in `chipflow.toml`", str(cm.exception)) @mock.patch("chipflow_lib._ensure_chipflow_root") @@ -132,9 +129,9 @@ def test_parse_config(self, mock_parse_config_file, mock_ensure_chipflow_root): """Test _parse_config which uses _ensure_chipflow_root and _parse_config_file""" mock_ensure_chipflow_root.return_value = "/mock/chipflow/root" mock_parse_config_file.return_value = {"chipflow": {"test": "value"}} - + config = _parse_config() - + mock_ensure_chipflow_root.assert_called_once() # We're expecting a string, not a Path mock_parse_config_file.assert_called_once_with("/mock/chipflow/root/chipflow.toml") diff --git a/tests/test_pin_lock.py b/tests/test_pin_lock.py index a085d5ad..00a2ed0a 100644 --- a/tests/test_pin_lock.py +++ b/tests/test_pin_lock.py @@ -2,11 +2,8 @@ import os import unittest from unittest import mock -import json import tempfile -from pathlib import Path -import pytest from chipflow_lib import ChipFlowError from chipflow_lib.pin_lock import ( @@ -23,10 +20,10 @@ def __init__(self, name="test_package"): self.allocated_pins = [] # Create a mock for the allocate method self.allocate = mock.MagicMock(side_effect=self._allocate) - + def sortpins(self, pins): return sorted(list(pins)) - + def _allocate(self, available, width): # Simple allocation - just return the first 'width' pins from available available_list = sorted(list(available)) @@ -40,7 +37,7 @@ def setUp(self): self.temp_dir = tempfile.TemporaryDirectory() self.original_cwd = os.getcwd() os.chdir(self.temp_dir.name) - + # Mock environment for testing self.chipflow_root_patcher = mock.patch.dict(os.environ, {"CHIPFLOW_ROOT": self.temp_dir.name}) self.chipflow_root_patcher.start() @@ -105,14 +102,14 @@ def test_allocate_pins_interface_with_annotation(self): } } pins = ["pin1", "pin2", "pin3", "pin4", "pin5", "pin6"] - + pin_map, remaining_pins = allocate_pins("test_interface", member_data, pins) - + # Check that correct pins were allocated self.assertIn("test_interface", pin_map) self.assertEqual(pin_map["test_interface"]["pins"], pins[:4]) self.assertEqual(pin_map["test_interface"]["direction"], "io") - + # Check remaining pins self.assertEqual(remaining_pins, pins[4:]) @@ -134,18 +131,18 @@ def test_allocate_pins_interface_without_annotation(self): } } pins = ["pin1", "pin2", "pin3", "pin4", "pin5", "pin6"] - + pin_map, remaining_pins = allocate_pins("test_interface", member_data, pins) - + # Check that correct pins were allocated self.assertIn("sub1", pin_map) self.assertEqual(pin_map["sub1"]["pins"], pins[:2]) self.assertEqual(pin_map["sub1"]["direction"], "i") - + self.assertIn("sub2", pin_map) self.assertEqual(pin_map["sub2"]["pins"], pins[2:5]) self.assertEqual(pin_map["sub2"]["direction"], "o") - + # Check remaining pins self.assertEqual(remaining_pins, pins[5:]) @@ -157,15 +154,15 @@ def test_allocate_pins_port(self): "dir": "i" } pins = ["pin1", "pin2", "pin3", "pin4"] - + pin_map, remaining_pins = allocate_pins("test_port", member_data, pins, port_name="my_port") - + # Check that correct pins were allocated self.assertIn("test_port", pin_map) self.assertEqual(pin_map["test_port"]["pins"], pins[:3]) self.assertEqual(pin_map["test_port"]["direction"], "i") self.assertEqual(pin_map["test_port"]["port_name"], "my_port") - + # Check remaining pins self.assertEqual(remaining_pins, pins[3:]) @@ -174,34 +171,34 @@ def test_pin_command_mocked(self, mock_lock_pins): """Test pin_command via mocking""" # Import here to avoid import issues during test collection from chipflow_lib.pin_lock import PinCommand - + # Create mock config mock_config = {"test": "config"} - + # Create command instance cmd = PinCommand(mock_config) - + # Create mock args mock_args = mock.Mock() mock_args.action = "lock" - + # Call run_cli cmd.run_cli(mock_args) - + # Verify lock_pins was called mock_lock_pins.assert_called_once() - + # Test build_cli_parser mock_parser = mock.Mock() mock_subparsers = mock.Mock() mock_parser.add_subparsers.return_value = mock_subparsers - + cmd.build_cli_parser(mock_parser) - + # Verify parser was built mock_parser.add_subparsers.assert_called_once() mock_subparsers.add_parser.assert_called_once() - + @mock.patch("builtins.open", new_callable=mock.mock_open) @mock.patch("chipflow_lib.pin_lock._parse_config") @mock.patch("chipflow_lib.pin_lock.top_interfaces") @@ -209,17 +206,17 @@ def test_pin_command_mocked(self, mock_lock_pins): @mock.patch("pathlib.Path.read_text") @mock.patch("chipflow_lib.pin_lock.PACKAGE_DEFINITIONS", new_callable=dict) @mock.patch("chipflow_lib.pin_lock.LockFile") - def test_lock_pins_new_lockfile(self, mock_lock_file, mock_package_defs, - mock_read_text, mock_exists, mock_top_interfaces, + def test_lock_pins_new_lockfile(self, mock_lock_file, mock_package_defs, + mock_read_text, mock_exists, mock_top_interfaces, mock_parse_config, mock_open): """Test lock_pins function creating a new lockfile""" # Setup mock package definitions mock_package_type = MockPackageType(name="cf20") mock_package_defs["cf20"] = mock_package_type - + # Setup mocks mock_exists.return_value = False # No existing pins.lock - + # Mock config mock_config = { "chipflow": { @@ -241,7 +238,7 @@ def test_lock_pins_new_lockfile(self, mock_lock_file, mock_package_defs, } } mock_parse_config.return_value = mock_config - + # Mock top_interfaces mock_interface = { "comp1": { @@ -259,32 +256,32 @@ def test_lock_pins_new_lockfile(self, mock_lock_file, mock_package_defs, } } mock_top_interfaces.return_value = (None, mock_interface) - + # Set up LockFile mock mock_lock_instance = mock.MagicMock() mock_lock_file.return_value = mock_lock_instance # Make model_dump_json return a valid JSON string mock_lock_instance.model_dump_json.return_value = '{"test": "json"}' - + # Import and run lock_pins from chipflow_lib.pin_lock import lock_pins - + # Mock the Package.__init__ to avoid validation errors with mock.patch("chipflow_lib.pin_lock.Package") as mock_package_class: mock_package_instance = mock.MagicMock() mock_package_class.return_value = mock_package_instance - + # Mock PortMap with mock.patch("chipflow_lib.pin_lock.PortMap") as mock_port_map_class: mock_port_map_instance = mock.MagicMock() mock_port_map_class.return_value = mock_port_map_instance - + # Run the function lock_pins() - + # Verify Package was initialized with our mock package type mock_package_class.assert_called_with(package_type=mock_package_type) - + # Check that add_pad was called for each pad calls = [ mock.call("clk", {"type": "clock", "loc": "1"}), @@ -293,20 +290,20 @@ def test_lock_pins_new_lockfile(self, mock_lock_file, mock_package_defs, mock.call("gnd", {"type": "ground", "loc": "4"}) ] mock_package_instance.add_pad.assert_has_calls(calls, any_order=True) - + # Verify port allocation happened self.assertTrue(mock_package_type.allocate.called) - + # Verify LockFile creation mock_lock_file.assert_called_once() - + # Check that open was called for writing mock_open.assert_called_once_with('pins.lock', 'w') - + # Verify write was called with the JSON data file_handle = mock_open.return_value.__enter__.return_value file_handle.write.assert_called_once_with('{"test": "json"}') - + @mock.patch("builtins.open", new_callable=mock.mock_open) @mock.patch("chipflow_lib.pin_lock._parse_config") @mock.patch("chipflow_lib.pin_lock.top_interfaces") @@ -315,31 +312,31 @@ def test_lock_pins_new_lockfile(self, mock_lock_file, mock_package_defs, @mock.patch("chipflow_lib.pin_lock.LockFile.model_validate_json") @mock.patch("chipflow_lib.pin_lock.PACKAGE_DEFINITIONS", new_callable=dict) @mock.patch("chipflow_lib.pin_lock.LockFile") - def test_lock_pins_with_existing_lockfile(self, mock_lock_file, mock_package_defs, - mock_validate_json, mock_read_text, - mock_exists, mock_top_interfaces, + def test_lock_pins_with_existing_lockfile(self, mock_lock_file, mock_package_defs, + mock_validate_json, mock_read_text, + mock_exists, mock_top_interfaces, mock_parse_config, mock_open): """Test lock_pins function with an existing pins.lock file""" # Setup mock package definitions mock_package_type = MockPackageType(name="cf20") mock_package_defs["cf20"] = mock_package_type - + # Setup mocks mock_exists.return_value = True # Existing pins.lock mock_read_text.return_value = '{"mock": "json"}' - + # Mock LockFile instance for validate_json mock_old_lock = mock.MagicMock() mock_old_lock.package.check_pad.return_value = None # No conflicting pads mock_old_lock.port_map.get_ports.return_value = None # No existing ports mock_validate_json.return_value = mock_old_lock - + # Set up LockFile mock for constructor mock_new_lock = mock.MagicMock() mock_lock_file.return_value = mock_new_lock # Make model_dump_json return a valid JSON string mock_new_lock.model_dump_json.return_value = '{"test": "json"}' - + # Mock config mock_config = { "chipflow": { @@ -361,7 +358,7 @@ def test_lock_pins_with_existing_lockfile(self, mock_lock_file, mock_package_def } } mock_parse_config.return_value = mock_config - + # Mock top_interfaces mock_interface = { "comp1": { @@ -379,32 +376,32 @@ def test_lock_pins_with_existing_lockfile(self, mock_lock_file, mock_package_def } } mock_top_interfaces.return_value = (None, mock_interface) - + # Import and run lock_pins from chipflow_lib.pin_lock import lock_pins - + # Mock the Package.__init__ to avoid validation errors with mock.patch("chipflow_lib.pin_lock.Package") as mock_package_class: mock_package_instance = mock.MagicMock() mock_package_class.return_value = mock_package_instance - + # Mock PortMap with mock.patch("chipflow_lib.pin_lock.PortMap") as mock_port_map_class: mock_port_map_instance = mock.MagicMock() mock_port_map_class.return_value = mock_port_map_instance - + # Run the function lock_pins() - + # Verify read_text was called to read the existing lockfile mock_read_text.assert_called_once() - + # Verify model_validate_json was called to parse the lockfile mock_validate_json.assert_called_once_with('{"mock": "json"}') - + # Verify Package was initialized with our mock package type mock_package_class.assert_called_with(package_type=mock_package_type) - + # Check that add_pad was called for each pad calls = [ mock.call("clk", {"type": "clock", "loc": "1"}), @@ -413,50 +410,50 @@ def test_lock_pins_with_existing_lockfile(self, mock_lock_file, mock_package_def mock.call("gnd", {"type": "ground", "loc": "4"}) ] mock_package_instance.add_pad.assert_has_calls(calls, any_order=True) - + # Verify LockFile creation mock_lock_file.assert_called_once() - + # Check that open was called for writing the new lockfile mock_open.assert_called_once_with('pins.lock', 'w') - + # Verify data was written file_handle = mock_open.return_value.__enter__.return_value file_handle.write.assert_called_once_with('{"test": "json"}') - + @mock.patch("chipflow_lib.pin_lock._parse_config") @mock.patch("pathlib.Path.exists") @mock.patch("pathlib.Path.read_text") @mock.patch("chipflow_lib.pin_lock.LockFile.model_validate_json") @mock.patch("chipflow_lib.pin_lock.PACKAGE_DEFINITIONS", new_callable=dict) @mock.patch("chipflow_lib.pin_lock.LockFile") - def test_lock_pins_with_conflicts(self, mock_lock_file, mock_package_defs, - mock_validate_json, mock_read_text, + def test_lock_pins_with_conflicts(self, mock_lock_file, mock_package_defs, + mock_validate_json, mock_read_text, mock_exists, mock_parse_config): """Test lock_pins function with conflicting pins in lockfile vs config""" # Setup mock package definitions mock_package_type = MockPackageType(name="cf20") mock_package_defs["cf20"] = mock_package_type - + # Setup mocks mock_exists.return_value = True # Existing pins.lock mock_read_text.return_value = '{"mock": "json"}' - + # Mock LockFile instance with conflicting pad mock_old_lock = mock.MagicMock() - + # Create a conflicting port class MockConflictPort: def __init__(self): self.pins = ["5"] # Different from config - + mock_old_lock.package.check_pad.return_value = MockConflictPort() mock_validate_json.return_value = mock_old_lock - + # Set up new LockFile mock for constructor (will not be reached in this test) mock_new_lock = mock.MagicMock() mock_lock_file.return_value = mock_new_lock - + # Mock config mock_config = { "chipflow": { @@ -474,25 +471,25 @@ def __init__(self): } } mock_parse_config.return_value = mock_config - + # Import lock_pins from chipflow_lib.pin_lock import lock_pins - + # Mock the Package.__init__ with mock.patch("chipflow_lib.pin_lock.Package") as mock_package_class: mock_package_instance = mock.MagicMock() mock_package_class.return_value = mock_package_instance - + # Test for exception with self.assertRaises(ChipFlowError) as cm: lock_pins() - + # Verify error message self.assertIn("chipflow.toml conflicts with pins.lock", str(cm.exception)) - + # Verify the exception is raised before we reach the LockFile constructor mock_lock_file.assert_not_called() - + @mock.patch("builtins.open", new_callable=mock.mock_open) @mock.patch("chipflow_lib.pin_lock._parse_config") @mock.patch("chipflow_lib.pin_lock.top_interfaces") @@ -501,23 +498,23 @@ def __init__(self): @mock.patch("chipflow_lib.pin_lock.LockFile.model_validate_json") @mock.patch("chipflow_lib.pin_lock.PACKAGE_DEFINITIONS", new_callable=dict) @mock.patch("chipflow_lib.pin_lock.LockFile") - def test_lock_pins_reuse_existing_ports(self, mock_lock_file, mock_package_defs, - mock_validate_json, mock_read_text, - mock_exists, mock_top_interfaces, + def test_lock_pins_reuse_existing_ports(self, mock_lock_file, mock_package_defs, + mock_validate_json, mock_read_text, + mock_exists, mock_top_interfaces, mock_parse_config, mock_open): """Test lock_pins function reusing existing port allocations""" # Setup mock package definitions mock_package_type = MockPackageType(name="cf20") mock_package_defs["cf20"] = mock_package_type - + # Setup mocks mock_exists.return_value = True # Existing pins.lock mock_read_text.return_value = '{"mock": "json"}' - + # Mock LockFile instance for existing lock mock_old_lock = mock.MagicMock() mock_old_lock.package.check_pad.return_value = None # No conflicting pads - + # Create existing ports to be reused existing_ports = { "tx": mock.MagicMock(pins=["10"]), @@ -525,13 +522,13 @@ def test_lock_pins_reuse_existing_ports(self, mock_lock_file, mock_package_defs, } mock_old_lock.port_map.get_ports.return_value = existing_ports mock_validate_json.return_value = mock_old_lock - + # Set up new LockFile mock for constructor mock_new_lock = mock.MagicMock() mock_lock_file.return_value = mock_new_lock # Make model_dump_json return a valid JSON string mock_new_lock.model_dump_json.return_value = '{"test": "json"}' - + # Mock config mock_config = { "chipflow": { @@ -547,7 +544,7 @@ def test_lock_pins_reuse_existing_ports(self, mock_lock_file, mock_package_defs, } } mock_parse_config.return_value = mock_config - + # Mock top_interfaces mock_interface = { "comp1": { @@ -565,35 +562,35 @@ def test_lock_pins_reuse_existing_ports(self, mock_lock_file, mock_package_defs, } } mock_top_interfaces.return_value = (None, mock_interface) - + # Import and run lock_pins from chipflow_lib.pin_lock import lock_pins - + # Mock the Package.__init__ to avoid validation errors with mock.patch("chipflow_lib.pin_lock.Package") as mock_package_class: mock_package_instance = mock.MagicMock() mock_package_class.return_value = mock_package_instance - + # Mock PortMap with mock.patch("chipflow_lib.pin_lock.PortMap") as mock_port_map_class: mock_port_map_instance = mock.MagicMock() mock_port_map_class.return_value = mock_port_map_instance - + # Run the function lock_pins() - + # Verify get_ports was called to retrieve existing ports mock_old_lock.port_map.get_ports.assert_called_with("comp1", "uart") - + # Verify existing ports were reused by calling add_ports mock_port_map_instance.add_ports.assert_called_with("comp1", "uart", existing_ports) - + # Verify LockFile creation with reused ports mock_lock_file.assert_called_once() - + # Check that open was called for writing mock_open.assert_called_once_with('pins.lock', 'w') - - # Verify data was written + + # Verify data was written file_handle = mock_open.return_value.__enter__.return_value file_handle.write.assert_called_once_with('{"test": "json"}') \ No newline at end of file diff --git a/tests/test_silicon_platform.py b/tests/test_silicon_platform.py index 9795e0e0..2cb2d1dd 100644 --- a/tests/test_silicon_platform.py +++ b/tests/test_silicon_platform.py @@ -4,7 +4,7 @@ import os import unittest -import tomli +import tomli from amaranth import * from amaranth.hdl._ir import Design diff --git a/tests/test_silicon_platform_port.py b/tests/test_silicon_platform_port.py index beb5987d..68930f4f 100644 --- a/tests/test_silicon_platform_port.py +++ b/tests/test_silicon_platform_port.py @@ -1,9 +1,8 @@ # amaranth: UnusedElaboratable=no # SPDX-License-Identifier: BSD-2-Clause import unittest -import pytest -from amaranth import Signal, Cat, Module +from amaranth import Signal, Module from amaranth.lib import wiring, io from amaranth.lib.wiring import PureInterface @@ -14,14 +13,14 @@ class TestSiliconPlatformPort(unittest.TestCase): def test_init_input_port(self): # Test initialization with input direction - port_obj = Port(type="input", pins=["1", "2", "3"], port_name="test_input", + port_obj = Port(type="input", pins=["1", "2", "3"], port_name="test_input", direction="i", options={}) spp = SiliconPlatformPort("comp", "test_input", port_obj) - + self.assertEqual(spp.direction, io.Direction.Input) self.assertEqual(len(spp), 3) # Should match the port width self.assertFalse(spp.invert) - + # Test accessing properties _ = spp.i # Should not raise an error with self.assertRaises(AttributeError): @@ -31,14 +30,14 @@ def test_init_input_port(self): def test_init_output_port(self): # Test initialization with output direction - port_obj = Port(type="output", pins=["1", "2"], port_name="test_output", + port_obj = Port(type="output", pins=["1", "2"], port_name="test_output", direction="o", options={}) spp = SiliconPlatformPort("comp", "test_output", port_obj) - + self.assertEqual(spp.direction, io.Direction.Output) self.assertEqual(len(spp), 2) # Should match the port width self.assertFalse(spp.invert) - + # Test accessing properties _ = spp.o # Should not raise an error _ = spp.oe # Should not raise an error since we now always have an _oe for outputs @@ -47,19 +46,19 @@ def test_init_output_port(self): def test_init_bidir_port(self): # Test initialization with bidirectional direction - port_obj = Port(type="bidir", pins=["1", "2", "3", "4"], port_name="test_bidir", + port_obj = Port(type="bidir", pins=["1", "2", "3", "4"], port_name="test_bidir", direction="io", options={"all_have_oe": False}) spp = SiliconPlatformPort("comp", "test_bidir", port_obj) - + self.assertEqual(spp.direction, io.Direction.Bidir) self.assertEqual(len(spp), 4) # Should match the port width self.assertFalse(spp.invert) - + # Check the signals have the correct widths self.assertEqual(len(spp.i), 4) self.assertEqual(len(spp.o), 4) self.assertEqual(len(spp.oe), 1) # Single OE for all pins - + # Test accessing properties _ = spp.i # Should not raise an error _ = spp.o # Should not raise an error @@ -67,147 +66,147 @@ def test_init_bidir_port(self): def test_init_bidir_port_all_have_oe(self): # Test initialization with bidirectional direction and all_have_oe=True - port_obj = Port(type="bidir", pins=["1", "2", "3"], port_name="test_bidir", + port_obj = Port(type="bidir", pins=["1", "2", "3"], port_name="test_bidir", direction="io", options={"all_have_oe": True}) spp = SiliconPlatformPort("comp", "test_bidir", port_obj) - + self.assertEqual(spp.direction, io.Direction.Bidir) self.assertEqual(len(spp), 3) # Should match the port width self.assertFalse(spp.invert) - + # Check the signals have the correct widths self.assertEqual(len(spp.i), 3) self.assertEqual(len(spp.o), 3) self.assertEqual(len(spp.oe), 3) # One OE per pin - + def test_len_input_port(self): # Test __len__ with input direction - port_obj = Port(type="input", pins=["1", "2", "3"], port_name="test_input", + port_obj = Port(type="input", pins=["1", "2", "3"], port_name="test_input", direction="i", options={}) spp = SiliconPlatformPort("comp", "test_input", port_obj) - + self.assertEqual(len(spp), 3) # Should match the port width - + def test_len_output_port(self): # Test __len__ with output direction - port_obj = Port(type="output", pins=["1", "2"], port_name="test_output", + port_obj = Port(type="output", pins=["1", "2"], port_name="test_output", direction="o", options={}) spp = SiliconPlatformPort("comp", "test_output", port_obj) - + self.assertEqual(len(spp), 2) # Should match the port width - + def test_len_bidir_port(self): # Test __len__ with bidirectional direction - port_obj = Port(type="bidir", pins=["1", "2", "3", "4"], port_name="test_bidir", + port_obj = Port(type="bidir", pins=["1", "2", "3", "4"], port_name="test_bidir", direction="io", options={"all_have_oe": False}) spp = SiliconPlatformPort("comp", "test_bidir", port_obj) - + self.assertEqual(len(spp), 4) # Should match the port width - + def test_len_bidir_port_all_have_oe(self): # Test __len__ with bidirectional direction and all_have_oe=True - port_obj = Port(type="bidir", pins=["1", "2", "3"], port_name="test_bidir", + port_obj = Port(type="bidir", pins=["1", "2", "3"], port_name="test_bidir", direction="io", options={"all_have_oe": True}) spp = SiliconPlatformPort("comp", "test_bidir", port_obj) - + self.assertEqual(len(spp), 3) # Should match the port width def test_getitem(self): # Test __getitem__ - port_obj = Port(type="bidir", pins=["1", "2", "3"], port_name="test_bidir", + port_obj = Port(type="bidir", pins=["1", "2", "3"], port_name="test_bidir", direction="io", options={"all_have_oe": True}) spp = SiliconPlatformPort("comp", "test_bidir", port_obj) - + # Get a slice of the port slice_port = spp[1] self.assertEqual(spp.direction, slice_port.direction) self.assertEqual(spp.invert, slice_port.invert) - + def test_invert(self): # Test __invert__ for a bidirectional port since it has all signal types - port_obj = Port(type="bidir", pins=["1", "2", "3"], port_name="test_bidir", + port_obj = Port(type="bidir", pins=["1", "2", "3"], port_name="test_bidir", direction="io", options={"all_have_oe": True}) spp = SiliconPlatformPort("comp", "test_bidir", port_obj) - + inverted_port = ~spp self.assertEqual(spp.direction, inverted_port.direction) self.assertNotEqual(spp.invert, inverted_port.invert) self.assertTrue(inverted_port.invert) - + def test_add(self): # Test __add__ - port_obj1 = Port(type="input", pins=["1", "2"], port_name="test_input1", + port_obj1 = Port(type="input", pins=["1", "2"], port_name="test_input1", direction="i", options={}) - port_obj2 = Port(type="input", pins=["3", "4"], port_name="test_input2", + port_obj2 = Port(type="input", pins=["3", "4"], port_name="test_input2", direction="i", options={}) spp1 = SiliconPlatformPort("comp", "test_input1", port_obj1) spp2 = SiliconPlatformPort("comp", "test_input2", port_obj2) - + combined_port = spp1 + spp2 self.assertEqual(spp1.direction, combined_port.direction) self.assertEqual(len(combined_port), len(spp1) + len(spp2)) - + def test_wire_input(self): # Test wire method with a mock input interface - port_obj = Port(type="input", pins=["1", "2", "3"], port_name="test_input", + port_obj = Port(type="input", pins=["1", "2", "3"], port_name="test_input", direction="i", options={}) spp = SiliconPlatformPort("comp", "test_input", port_obj) - + # Create a mock interface class MockSignature(wiring.Signature): def __init__(self): super().__init__({"i": wiring.In(3)}) self._direction = io.Direction.Input - + @property def direction(self): return self._direction - + class MockInterface(PureInterface): def __init__(self): self.signature = MockSignature() self.i = Signal(3) - + interface = MockInterface() m = Module() - + # Wire should not raise an exception spp.wire(m, interface) - + def test_wire_output(self): # Test wire method with a mock output interface to cover line 105 - port_obj = Port(type="output", pins=["1", "2"], port_name="test_output", + port_obj = Port(type="output", pins=["1", "2"], port_name="test_output", direction="o", options={}) spp = SiliconPlatformPort("comp", "test_output", port_obj) - + # Create a mock interface class MockSignature(wiring.Signature): def __init__(self): super().__init__({"o": wiring.Out(2)}) self._direction = io.Direction.Output - + @property def direction(self): return self._direction - + class MockInterface(PureInterface): def __init__(self): self.signature = MockSignature() self.o = Signal(2) self.oe = Signal(1) - + interface = MockInterface() m = Module() - + # Wire should not raise an exception spp.wire(m, interface) - + def test_wire_bidir(self): # Test wire method with a mock bidirectional interface to cover both cases - port_obj = Port(type="bidir", pins=["1", "2", "3"], port_name="test_bidir", + port_obj = Port(type="bidir", pins=["1", "2", "3"], port_name="test_bidir", direction="io", options={"all_have_oe": True}) spp = SiliconPlatformPort("comp", "test_bidir", port_obj) - + # Create a mock interface class MockSignature(wiring.Signature): def __init__(self): @@ -217,33 +216,33 @@ def __init__(self): "oe": wiring.Out(3), }) self._direction = io.Direction.Bidir - + @property def direction(self): return self._direction - + class MockInterface(PureInterface): def __init__(self): self.signature = MockSignature() self.i = Signal(3) self.o = Signal(3) self.oe = Signal(3) - + interface = MockInterface() m = Module() - + # Wire should not raise an exception spp.wire(m, interface) - + def test_repr(self): # Test the __repr__ method for a bidirectional port - port_obj = Port(type="bidir", pins=["1", "2", "3"], port_name="test_bidir", + port_obj = Port(type="bidir", pins=["1", "2", "3"], port_name="test_bidir", direction="io", options={"all_have_oe": True}) spp = SiliconPlatformPort("comp", "test_bidir", port_obj) - + # Get the representation repr_str = repr(spp) - + # Check that it contains expected elements self.assertIn("SiliconPlatformPort", repr_str) self.assertIn("direction", repr_str) diff --git a/tests/test_steps_silicon.py b/tests/test_steps_silicon.py index 1193d938..3ce20564 100644 --- a/tests/test_steps_silicon.py +++ b/tests/test_steps_silicon.py @@ -5,15 +5,10 @@ import unittest from unittest import mock import argparse -import json import tempfile -from pathlib import Path -import pytest -import requests -from amaranth import Module, Signal -from amaranth.lib import io +from amaranth import Module from chipflow_lib import ChipFlowError from chipflow_lib.steps.silicon import SiliconStep, SiliconTop @@ -25,13 +20,13 @@ def setUp(self): self.temp_dir = tempfile.TemporaryDirectory() self.original_cwd = os.getcwd() os.chdir(self.temp_dir.name) - + # Mock environment for testing self.chipflow_root_patcher = mock.patch.dict( os.environ, {"CHIPFLOW_ROOT": self.temp_dir.name} ) self.chipflow_root_patcher.start() - + # Create basic config for tests self.config = { "chipflow": { @@ -63,7 +58,7 @@ def tearDown(self): def test_init(self, mock_silicontop_class): """Test SiliconStep initialization""" step = SiliconStep(self.config) - + # Check that attributes are correctly set self.assertEqual(step.config, self.config) self.assertEqual(step.project_name, "test_project") @@ -98,7 +93,7 @@ def test_prepare(self, mock_top_interfaces, mock_platform_class, mock_silicontop # Verify the name parameter self.assertEqual(kwargs["name"], "test_project") self.assertEqual(mock_silicontop_class.call_args[0][0], self.config) - + # Check result self.assertEqual(result, "/path/to/rtlil") @@ -108,13 +103,13 @@ def test_build_cli_parser(self): parser = mock.MagicMock() subparsers = mock.MagicMock() parser.add_subparsers.return_value = subparsers - + # Create SiliconStep instance step = SiliconStep(self.config) - + # Call the method step.build_cli_parser(parser) - + # Verify parser setup parser.add_subparsers.assert_called_once_with(dest="action") # Check that prepare and submit subparsers were added @@ -147,7 +142,7 @@ def test_cli_prepare(self, mock_prepare, mock_submit, mock_dotenv, mock_top_inte # Call the method step.run_cli(args) - + mock_prepare.assert_called_once() mock_submit.assert_not_called() # Verify dotenv not loaded for prepare @@ -161,7 +156,7 @@ def test_run_cli_submit(self, mock_load_dotenv, mock_submit, mock_prepare, mock_ """Test run_cli with submit action""" # Setup mocks mock_prepare.return_value = "/path/to/rtlil" - + # Add environment variables with mock.patch.dict(os.environ, { "CHIPFLOW_API_KEY_ID": "api_key_id", @@ -171,13 +166,13 @@ def test_run_cli_submit(self, mock_load_dotenv, mock_submit, mock_prepare, mock_ args = mock.MagicMock() args.action = "submit" args.dry_run = False - + # Create SiliconStep instance step = SiliconStep(self.config) - + # Call the method step.run_cli(args) - + # Verify prepare and submit were called mock_prepare.assert_called_once() mock_submit.assert_called_once_with("/path/to/rtlil", dry_run=False) @@ -196,18 +191,18 @@ def test_run_cli_submit_dry_run(self, mock_top_interfaces, mock_load_dotenv, moc mock_platform.build.return_value = "/path/to/rtlil" mock_top_interfaces.return_value = ({"mock_component": mock.MagicMock()}, {}) mock_platform.pinlock.port_map = {} - + # Create mock args args = mock.MagicMock() args.action = "submit" args.dry_run = True - + # Create SiliconStep instance step = SiliconStep(self.config) - + # Call the method step.run_cli(args) - + # Verify prepare and submit were called mock_platform.build.assert_called_once() mock_submit.assert_called_once_with("/path/to/rtlil", dry_run=True) @@ -231,7 +226,7 @@ def test_run_cli_submit_missing_project_name(self, mock_load_dotenv, mock_prepar } } } - + # Add environment variables with mock.patch.dict(os.environ, { "CHIPFLOW_API_KEY_ID": "api_key_id", @@ -241,14 +236,14 @@ def test_run_cli_submit_missing_project_name(self, mock_load_dotenv, mock_prepar args = mock.MagicMock() args.action = "submit" args.dry_run = False - + # Create SiliconStep instance step = SiliconStep(config_no_project) - + # Test for exception with self.assertRaises(ChipFlowError) as cm: step.run_cli(args) - + # Verify error message mentions project_id self.assertIn("project_id", str(cm.exception)) # Verify dotenv was loaded @@ -262,14 +257,14 @@ def test_run_cli_submit_missing_api_keys(self, mock_load_dotenv, mock_prepare): args = mock.MagicMock() args.action = "submit" args.dry_run = False - + # Create SiliconStep instance step = SiliconStep(self.config) - + # Test for exception with self.assertRaises(ChipFlowError) as cm: step.run_cli(args) - + # Verify error message self.assertIn("CHIPFLOW_API_KEY_ID", str(cm.exception)) self.assertIn("CHIPFLOW_API_KEY_SECRET", str(cm.exception)) @@ -285,39 +280,39 @@ def test_submit_dry_run(self, mock_version, mock_check_output): "abcdef\n", # git rev-parse "" # git status (not dirty) ] - + # Setup version mocks mock_version.return_value = "1.0.0" - + # Setup platform mock platform_mock = mock.MagicMock() platform_mock._ports = { "port1": mock.MagicMock( - pins=["1"], + pins=["1"], direction=mock.MagicMock(value="i") ), "port2": mock.MagicMock( - pins=["2", "3"], + pins=["2", "3"], direction=mock.MagicMock(value="o") ) } - + # Create SiliconStep with mocked platform step = SiliconStep(self.config) step.platform = platform_mock - + # Mock print and capture output with mock.patch("builtins.print") as mock_print: # Call submit with dry run step.submit("/path/to/rtlil", dry_run=True) - + # Verify print was called twice self.assertEqual(mock_print.call_count, 2) # Verify JSON data was printed args = mock_print.call_args_list self.assertIn("data=", args[0][0][0]) self.assertIn("files['config']=", args[1][0][0]) - + # Verify no requests were made self.assertFalse(hasattr(step, "_request_made")) @@ -334,29 +329,29 @@ def test_submit_success(self, mock_file_open, mock_post, mock_check_output, "abcdef\n", # git rev-parse "M file.py" # git status (dirty) ] - + # Setup version mocks mock_version.return_value = "1.0.0" - + # Setup response mock mock_response = mock.MagicMock() mock_response.status_code = 200 mock_response.json.return_value = {"build_id": "12345"} mock_post.return_value = mock_response - + # Setup platform mock platform_mock = mock_platform_class.return_value platform_mock._ports = { "port1": mock.MagicMock( - pins=["1"], + pins=["1"], direction=mock.MagicMock(value="i") ), "port2": mock.MagicMock( - pins=["2", "3"], + pins=["2", "3"], direction=mock.MagicMock(value="o") ) } - + # Add required environment variables with mock.patch.dict(os.environ, { "CHIPFLOW_API_KEY_ID": "api_key_id", @@ -364,12 +359,12 @@ def test_submit_success(self, mock_file_open, mock_post, mock_check_output, }): # Create SiliconStep with mocked platform step = SiliconStep(self.config) - + # Mock print and capture output with mock.patch("builtins.print") as mock_print: # Call submit step.submit("/path/to/rtlil") - + # Verify requests.post was called mock_post.assert_called_once() # Check auth was provided @@ -378,10 +373,10 @@ def test_submit_success(self, mock_file_open, mock_post, mock_check_output, # Check files were included self.assertIn("rtlil", kwargs["files"]) self.assertIn("config", kwargs["files"]) - + # Verify file was opened mock_file_open.assert_called_with("/path/to/rtlil", "rb") - + # Verify build URL was printed mock_print.assert_called_once() self.assertIn("build/12345", mock_print.call_args[0][0]) @@ -398,10 +393,10 @@ def test_submit_error(self, mock_file_open, mock_post, mock_version, mock_check_ "abcdef\n", # git rev-parse "" # git status (not dirty) ] - + # Setup version mocks mock_version.return_value = "1.0.0" - + # Setup response mock with error mock_response = mock.MagicMock() mock_response.status_code = 400 @@ -411,16 +406,16 @@ def test_submit_error(self, mock_file_open, mock_post, mock_version, mock_check_ mock_response.request.headers = {"Authorization": "Basic xyz"} mock_response.headers = {"Content-Type": "application/json"} mock_post.return_value = mock_response - + # Setup platform mock platform_mock = mock_platform_class.return_value platform_mock._ports = { "port1": mock.MagicMock( - pins=["1"], + pins=["1"], direction=mock.MagicMock(value="i") ), } - + # Add required environment variables with mock.patch.dict(os.environ, { "CHIPFLOW_API_KEY_ID": "api_key_id", @@ -428,14 +423,14 @@ def test_submit_error(self, mock_file_open, mock_post, mock_version, mock_check_ }): # Create SiliconStep with mocked platform step = SiliconStep(self.config) - + # Test for exception with self.assertRaises(ChipFlowError) as cm: step.submit("/path/to/rtlil") - + # Verify error message self.assertIn("Failed to submit design", str(cm.exception)) - + # Verify requests.post was called mock_post.assert_called_once() @@ -466,7 +461,7 @@ def test_init(self): """Test SiliconTop initialization""" top = SiliconTop(self.config) self.assertEqual(top._config, self.config) - + @mock.patch("chipflow_lib.steps.silicon.top_interfaces") def test_elaborate(self, mock_top_interfaces): """Test SiliconTop elaborate method""" @@ -483,33 +478,33 @@ def test_elaborate(self, mock_top_interfaces): "test_port": mock.MagicMock(), "heartbeat": mock.MagicMock() } - + # Create mock components and interfaces mock_component = mock.MagicMock() mock_component.iface1.port1 = mock.MagicMock() mock_components = {"comp1": mock_component} - + # Setup top_interfaces mock mock_top_interfaces.return_value = (mock_components, {}) - + # Create SiliconTop instance top = SiliconTop(self.config) - + # Call elaborate module = top.elaborate(platform) - + # Verify it's a Module self.assertIsInstance(module, Module) - + # Use the result to avoid UnusedElaboratable warning self.assertIsNotNone(module) - + # Verify platform methods were called platform.instantiate_ports.assert_called_once() - + # Verify port wiring platform.ports["test_port"].wire.assert_called_once() - + # Verify heartbeat was created (since debug.heartbeat is True) platform.request.assert_called_with("heartbeat") @@ -569,12 +564,11 @@ def test_elaborate_no_heartbeat(self, mock_top_interfaces, mock_platform_class): def test_heartbeat(self, mock_top_interfaces, mock_module, mock_heartbeat_class, mock_io_buffer): """Test that Heartbeat class gets used properly when debug.heartbeat is True""" # Import Heartbeat class to make sure it's loaded and used - from chipflow_lib.platforms.silicon import Heartbeat - + # Create a mock Heartbeat instance mock_heartbeat = mock.MagicMock() mock_heartbeat_class.return_value = mock_heartbeat - + # Create a mock platform with a heartbeat port platform = mock.MagicMock() platform.pinlock.port_map = {} @@ -582,14 +576,14 @@ def test_heartbeat(self, mock_top_interfaces, mock_module, mock_heartbeat_class, "heartbeat": mock.MagicMock() } platform.request.return_value = platform.ports["heartbeat"] - + # Create a mock for top_interfaces mock_top_interfaces.return_value = ({}, {}) - + # Create and elaborate SiliconTop with heartbeat top = SiliconTop(self.config) result = top.elaborate(platform) - + # Verify platform.request was called with "heartbeat" platform.request.assert_called_with("heartbeat") diff --git a/tests/test_utils_additional.py b/tests/test_utils_additional.py index 37e4253c..8a95cbbd 100644 --- a/tests/test_utils_additional.py +++ b/tests/test_utils_additional.py @@ -3,26 +3,17 @@ # SPDX-License-Identifier: BSD-2-Clause import unittest from unittest import mock -import itertools -import logging -from pathlib import Path from amaranth.lib import io -from pydantic import BaseModel from chipflow_lib import ChipFlowError from chipflow_lib.platforms.utils import ( - _chipflow_schema_uri, + _chipflow_schema_uri, _PinAnnotationModel, _PinAnnotation, PIN_ANNOTATION_SCHEMA, PinSignature, - OutputPinSignature, - InputPinSignature, - BidirPinSignature, _Side, - _group_consecutive_items, - _find_contiguous_sequence, _BasePackageDef, _BareDiePackageDef, _QuadPackageDef, @@ -38,7 +29,7 @@ def test_chipflow_schema_uri(self): """Test _chipflow_schema_uri function""" uri = _chipflow_schema_uri("test-schema", 1) self.assertEqual(uri, "https://api.chipflow.com/schemas/1/test-schema") - + def test_side_str(self): """Test _Side.__str__ method""" for side in _Side: @@ -48,12 +39,12 @@ def test_pin_annotation_model(self): """Test _PinAnnotationModel class""" # Test initialization model = _PinAnnotationModel(direction=io.Direction.Output, width=32, options={"opt1": "val1"}) - + # Check properties self.assertEqual(model.direction, "o") self.assertEqual(model.width, 32) self.assertEqual(model.options, {"opt1": "val1"}) - + # Test _annotation_schema class method schema = _PinAnnotationModel._annotation_schema() self.assertEqual(schema["$schema"], "https://json-schema.org/draft/2020-12/schema") @@ -63,14 +54,14 @@ def test_pin_annotation(self): """Test _PinAnnotation class""" # Test initialization annotation = _PinAnnotation(direction=io.Direction.Input, width=16) - + # Check model self.assertEqual(annotation.model.direction, "i") self.assertEqual(annotation.model.width, 16) - + # Test origin property self.assertEqual(annotation.origin, annotation.model) - + # Test as_json method json_data = annotation.as_json() self.assertEqual(json_data["direction"], "i") @@ -84,47 +75,47 @@ def test_pin_signature_properties(self): # Create signature with options options = {"all_have_oe": True, "init": 0} sig = PinSignature(io.Direction.Bidir, width=4, all_have_oe=True, init=0) - + # Test properties self.assertEqual(sig.direction, io.Direction.Bidir) self.assertEqual(sig.width(), 4) self.assertEqual(sig.options(), options) - + # Test __repr__ - actual representation depends on Direction enum's representation repr_string = repr(sig) self.assertIn("PinSignature", repr_string) self.assertIn("4", repr_string) self.assertIn("all_have_oe=True", repr_string) self.assertIn("init=0", repr_string) - + def test_pin_signature_annotations(self): """Test PinSignature annotations method""" # Create signature sig = PinSignature(io.Direction.Output, width=8, init=42) - + # Create a mock object to pass to annotations mock_obj = object() - + # Get annotations with the mock object annotations = sig.annotations(mock_obj) - + # Should return a tuple with at least one annotation self.assertIsInstance(annotations, tuple) self.assertGreater(len(annotations), 0) - + # Find PinAnnotation in annotations pin_annotation = None for annotation in annotations: if isinstance(annotation, _PinAnnotation): pin_annotation = annotation break - + # Verify the PinAnnotation was found and has correct values self.assertIsNotNone(pin_annotation, "PinAnnotation not found in annotations") self.assertEqual(pin_annotation.model.direction, "o") self.assertEqual(pin_annotation.model.width, 8) self.assertEqual(pin_annotation.model.options["init"], 42) - + # Call multiple times to ensure we don't get duplicate annotations annotations1 = sig.annotations(mock_obj) annotations2 = sig.annotations(mock_obj) @@ -142,7 +133,7 @@ def test_portmap_creation(self): # Create port port1 = Port(type="input", pins=["1"], port_name="test_port", direction="i") port2 = Port(type="output", pins=["2"], port_name="port2", direction="o") - + # Create a dictionary with the right structure data = { "comp1": { @@ -152,66 +143,66 @@ def test_portmap_creation(self): } } } - + # Create a PortMap port_map = PortMap(data) - + # Basic checks self.assertEqual(len(port_map), 1) self.assertIn("comp1", port_map) self.assertIn("iface1", port_map["comp1"]) self.assertIn("port1", port_map["comp1"]["iface1"]) self.assertEqual(port_map["comp1"]["iface1"]["port1"], port1) - + def test_portmap_mutable_mapping(self): """Test PortMap MutableMapping methods""" # Create an empty PortMap port_map = PortMap({}) - + # Test __setitem__ and __getitem__ port_map["comp1"] = {"iface1": {"port1": Port(type="input", pins=["1"], port_name="port1")}} self.assertIn("comp1", port_map) self.assertEqual(port_map["comp1"]["iface1"]["port1"].pins, ["1"]) - + # Test __delitem__ del port_map["comp1"] self.assertNotIn("comp1", port_map) - + # Test __iter__ and __len__ port_map["comp1"] = {"iface1": {}} port_map["comp2"] = {"iface2": {}} self.assertEqual(len(port_map), 2) self.assertEqual(set(port_map), {"comp1", "comp2"}) - + def test_portmap_methods(self): """Test PortMap helper methods""" # Create an empty PortMap port_map = PortMap({}) - + # Test add_port with a new component and interface port1 = Port(type="input", pins=["1"], port_name="port1", direction="i") port_map.add_port("comp1", "iface1", "port1", port1) - + self.assertIn("comp1", port_map) self.assertIn("iface1", port_map["comp1"]) self.assertIn("port1", port_map["comp1"]["iface1"]) self.assertEqual(port_map["comp1"]["iface1"]["port1"], port1) - + # Test add_ports with a new interface ports = { "port2": Port(type="output", pins=["2"], port_name="port2", direction="o"), "port3": Port(type="output", pins=["3"], port_name="port3", direction="o") } port_map.add_ports("comp1", "iface2", ports) - + self.assertIn("iface2", port_map["comp1"]) self.assertEqual(len(port_map["comp1"]["iface2"]), 2) self.assertEqual(port_map["comp1"]["iface2"]["port2"].pins, ["2"]) - + # Test get_ports result = port_map.get_ports("comp1", "iface1") self.assertEqual(result, {"port1": port1}) - + # Test get_ports with non-existent component result = port_map.get_ports("non_existent", "iface1") self.assertIsNone(result) @@ -222,25 +213,25 @@ def test_quad_package_def(self): """Test _QuadPackageDef class""" # Create instance quad_pkg = _QuadPackageDef(name="test_quad", width=5, height=5) - + # Check properties self.assertEqual(quad_pkg.name, "test_quad") self.assertEqual(quad_pkg.width, 5) self.assertEqual(quad_pkg.height, 5) - + # Check pins - formula depends on implementation details pins = quad_pkg.pins self.assertGreaterEqual(len(pins), 19) # At least the expected pins self.assertTrue(all(isinstance(p, str) for p in pins)) - + # Create a list of pins that can be sorted by int test_pins = ["1", "2", "3", "4", "5"] - + # Mock implementation of sortpins instead of calling the real one # which might have issues mock_sorted = sorted(test_pins, key=int) self.assertEqual(mock_sorted, ["1", "2", "3", "4", "5"]) - + def test_base_package_def_sortpins_bug(self): """Test _BasePackageDef sortpins method - documenting the bug""" # Create a minimal subclass of _BasePackageDef for testing @@ -248,66 +239,66 @@ class TestPackageDef(_BasePackageDef): @property def pins(self): return {"1", "2", "3"} - + def allocate(self, available, width): return list(available)[:width] - + # Create an instance pkg = TestPackageDef(name="test_pkg") - + # Instead of using SiliconTop to test elaboratables, let's use a simple mock # This avoids the need to import and use SiliconTop which generates warnings elaboratable_mock = mock.MagicMock() elaboratable_mock.elaborate = mock.MagicMock(return_value=mock.MagicMock()) - + # Test sortpins method - THIS IS EXPECTED TO FAIL because of a bug # The method should return sorted(list(pins)) but actually returns None # because list.sort() sorts in-place and returns None result = pkg.sortpins(["3", "1", "2"]) - + # This test documents the bug - the method returns None instead of a sorted list self.assertIsNone(result, "This documents a bug in sortpins! It should return a sorted list.") - + def test_bare_die_package_def(self): """Test _BareDiePackageDef class""" # Create instance bare_pkg = _BareDiePackageDef(name="test_bare", width=3, height=2) - + # Check properties self.assertEqual(bare_pkg.name, "test_bare") self.assertEqual(bare_pkg.width, 3) self.assertEqual(bare_pkg.height, 2) - + # Check pins pins = bare_pkg.pins self.assertEqual(len(pins), 10) # (3*2 + 2*2) pins - + @mock.patch('chipflow_lib.platforms.utils._BareDiePackageDef.sortpins') def test_cf20_package_def(self, mock_sortpins): """Test CF20 package definition""" # Mock the sortpins method to return a sorted list mock_sortpins.side_effect = lambda pins: sorted(list(pins)) - + # Get the CF20 package definition from PACKAGE_DEFINITIONS self.assertIn("cf20", PACKAGE_DEFINITIONS) cf20_pkg = PACKAGE_DEFINITIONS["cf20"] - + # Check that it's a BareDiePackageDef self.assertIsInstance(cf20_pkg, _BareDiePackageDef) - + # Check properties self.assertEqual(cf20_pkg.name, "cf20") self.assertEqual(cf20_pkg.width, 7) self.assertEqual(cf20_pkg.height, 3) - + # Check pins - CF20 should have 7*2 + 3*2 = 20 pins pins = cf20_pkg.pins self.assertEqual(len(pins), 20) - + # Test ordered_pins property self.assertTrue(hasattr(cf20_pkg, '_ordered_pins')) self.assertEqual(len(cf20_pkg._ordered_pins), 20) - + # This part of the test would need _find_contiguous_sequence to be tested separately # since there's a bug in the sortpins implementation @@ -317,10 +308,10 @@ def test_package_init(self): """Test Package initialization""" # Create package type package_type = _QuadPackageDef(name="test_package", width=10, height=10) - + # Create package package = Package(package_type=package_type) - + # Check properties self.assertEqual(package.package_type, package_type) self.assertEqual(package.power, {}) @@ -331,78 +322,78 @@ def test_package_add_pad(self): """Test Package.add_pad method""" # Create package type package_type = _QuadPackageDef(name="test_package", width=10, height=10) - + # Create package package = Package(package_type=package_type) - + # Add different pad types package.add_pad("clk1", {"type": "clock", "loc": "1"}) package.add_pad("rst1", {"type": "reset", "loc": "2"}) package.add_pad("vdd", {"type": "power", "loc": "3"}) package.add_pad("gnd", {"type": "ground", "loc": "4"}) package.add_pad("io1", {"type": "io", "loc": "5"}) - + # Check that pads were added correctly self.assertIn("clk1", package.clocks) self.assertEqual(package.clocks["clk1"].pins, ["1"]) - + self.assertIn("rst1", package.resets) self.assertEqual(package.resets["rst1"].pins, ["2"]) - + self.assertIn("vdd", package.power) self.assertEqual(package.power["vdd"].pins, ["3"]) - + self.assertIn("gnd", package.power) self.assertEqual(package.power["gnd"].pins, ["4"]) - + # io pad should not be added to any of the special collections self.assertNotIn("io1", package.clocks) self.assertNotIn("io1", package.resets) self.assertNotIn("io1", package.power) - + def test_package_check_pad(self): """Test Package.check_pad method""" # Create package type package_type = _QuadPackageDef(name="test_package", width=10, height=10) - + # Create package package = Package(package_type=package_type) - + # Add different pad types package.add_pad("clk1", {"type": "clock", "loc": "1"}) package.add_pad("rst1", {"type": "reset", "loc": "2"}) package.add_pad("vdd", {"type": "power", "loc": "3"}) package.add_pad("gnd", {"type": "ground", "loc": "4"}) - + # Test check_pad with different pad types clock_port = package.check_pad("clk1", {"type": "clock"}) self.assertIsNotNone(clock_port) self.assertEqual(clock_port.pins, ["1"]) - + reset_port = package.check_pad("rst1", {"type": "reset"}) self.assertIsNone(reset_port) # This is None due to a bug in the code - + power_port = package.check_pad("vdd", {"type": "power"}) self.assertIsNotNone(power_port) self.assertEqual(power_port.pins, ["3"]) - + ground_port = package.check_pad("gnd", {"type": "ground"}) self.assertIsNotNone(ground_port) self.assertEqual(ground_port.pins, ["4"]) - + # Test with unknown type unknown_port = package.check_pad("io1", {"type": "io"}) self.assertIsNone(unknown_port) - + # Test with non-existent pad nonexistent_port = package.check_pad("nonexistent", {"type": "clock"}) self.assertIsNone(nonexistent_port) - + def test_port_width(self): """Test Port.width property""" # Create port with multiple pins port = Port(type="test", pins=["1", "2", "3"], port_name="test_port") - + # Check width self.assertEqual(port.width, 3) @@ -414,7 +405,7 @@ class TestTopInterfaces(unittest.TestCase): def test_top_interfaces(self, mock_get_cls, mock_silicontop_class): """Test top_interfaces function""" from chipflow_lib.platforms.utils import top_interfaces - + # Create mock config without the problematic component that triggers an assertion config = { "chipflow": { @@ -424,37 +415,37 @@ def test_top_interfaces(self, mock_get_cls, mock_silicontop_class): } } } - + # Create mock classes mock_class1 = mock.MagicMock() mock_class1_instance = mock.MagicMock() mock_class1.return_value = mock_class1_instance mock_class1_instance.metadata.as_json.return_value = {"meta1": "value1"} mock_class1_instance.metadata.origin.signature.members = ["member1", "member2"] - + mock_class2 = mock.MagicMock() mock_class2_instance = mock.MagicMock() mock_class2.return_value = mock_class2_instance mock_class2_instance.metadata.as_json.return_value = {"meta2": "value2"} mock_class2_instance.metadata.origin.signature.members = ["member3"] - + # Setup mock to return different classes for different references def side_effect(ref, context=None): if ref == "module.Class1": return mock_class1 elif ref == "module.Class2": return mock_class2 - + mock_get_cls.side_effect = side_effect - + # Call top_interfaces top, interfaces = top_interfaces(config) - + # Check results self.assertEqual(len(top), 2) self.assertIn("comp1", top) self.assertIn("comp2", top) - + self.assertEqual(len(interfaces), 2) self.assertEqual(interfaces["comp1"], {"meta1": "value1"}) self.assertEqual(interfaces["comp2"], {"meta2": "value2"}) @@ -469,16 +460,16 @@ def test_load_pinlock_exists(self, mock_read_text, mock_exists, mock_ensure_chip """Test load_pinlock when pins.lock exists""" # Import here to avoid issues during test collection from chipflow_lib.platforms.utils import load_pinlock - + # Setup mocks mock_ensure_chipflow_root.return_value = "/mock/chipflow/root" mock_exists.return_value = True mock_read_text.return_value = '{"json": "content"}' mock_validate_json.return_value = "parsed_lock_file" - + # Call load_pinlock result = load_pinlock() - + # Check results self.assertEqual(result, "parsed_lock_file") mock_ensure_chipflow_root.assert_called_once() @@ -490,15 +481,15 @@ def test_load_pinlock_not_exists(self, mock_read_text, mock_exists, mock_ensure_ """Test load_pinlock when pins.lock doesn't exist""" # Import here to avoid issues during test collection from chipflow_lib.platforms.utils import load_pinlock - + # Setup mocks mock_ensure_chipflow_root.return_value = "/mock/chipflow/root" mock_exists.return_value = False - + # Call load_pinlock - should raise ChipFlowError with self.assertRaises(ChipFlowError) as cm: load_pinlock() - + # Check error message self.assertIn("Lockfile pins.lock not found", str(cm.exception)) mock_ensure_chipflow_root.assert_called_once()