Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
# Copyright 2023 Efabless Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import pytest
from decimal import Decimal
from dataclasses import dataclass
from pyfakefs.fake_filesystem_unittest import Patcher
from typing import Dict, List, Literal, Optional, Tuple, Type, Union
pytestmark = pytest.mark.all
@pytest.fixture
def _mock_fs():
with Patcher() as patcher:
patcher.fs.create_dir("/cwd")
os.chdir("/cwd")
patcher.fs.create_file("/cwd/a")
patcher.fs.create_file("/cwd/b")
yield
def test_macro_validation():
from librelane.config import Macro
with pytest.raises(ValueError, match="at least one GDSII file"):
Macro(gds=[], lef=["test"])
with pytest.raises(ValueError, match="at least one LEF file"):
Macro(gds=["test"], lef=[])
with pytest.raises(TypeError, match="got an unexpected keyword argument"):
Macro(gds=["test"], lef=["test"], lefs=[])
def test_macro_from_state():
from librelane.common import Path
from librelane.config import Macro
from librelane.state import State
state_in = State(
{
"nl": Path._dummy_path,
"pnl": Path._dummy_path,
"def": Path._dummy_path,
"odb": Path._dummy_path,
"sdf": {
"corner_1": Path._dummy_path,
"corner_2": Path._dummy_path,
},
"spef": {
"corner_*": Path._dummy_path,
},
"lib": {
"corner_1": Path._dummy_path,
"corner_2": Path._dummy_path,
},
"gds": Path._dummy_path,
},
metrics={},
)
with pytest.raises(
ValueError,
match="Macro cannot be made out of input state: View lef is missing",
):
Macro.from_state(state_in)
state_fixed = State(state_in, overrides={"lef": Path._dummy_path})
macro = Macro.from_state(state_fixed)
assert macro == Macro(
gds=[Path("__librelane_dummy_path")],
lef=[Path("__librelane_dummy_path")],
instances={},
nl=[Path("__librelane_dummy_path")],
pnl=[Path("__librelane_dummy_path")],
spef={"corner_*": [Path("__librelane_dummy_path")]},
lib={
"corner_1": [Path("__librelane_dummy_path")],
"corner_2": [Path("__librelane_dummy_path")],
},
spice=[],
sdf={
"corner_1": [Path("__librelane_dummy_path")],
"corner_2": [Path("__librelane_dummy_path")],
},
json_h=None,
), "Macro was not derived from State correctly"
def test_is_optional():
from librelane.config.variable import is_optional
assert is_optional(int) is False, "is_optional false positive"
assert is_optional(Optional[int]) is True, "is_optional false negative"
assert (
is_optional(Optional[Union[int, dict]]) is True
), "is_optional composite false negative"
assert (
is_optional(Union[None, int, dict]) is True
), "is_optional flattened union false negative"
assert is_optional(int | None) is True
assert is_optional(int | dict | None) is True
def test_some_of():
from librelane.config.variable import some_of
assert some_of(int) is int, "some_of changed the type of a non-option type"
assert (
some_of(List[str]) == List[str]
), "some_of changed the type of a non-option type"
assert (
some_of(Optional[int]) is int
), "some_of failed to extract type from option type"
assert (
some_of(Optional[Union[Dict, List]]) is Union[Dict, List]
), "some of failed to properly handle optional union"
assert (
some_of(Union[Dict, List, None]) is Union[Dict, List]
), "some of failed to properly handle flattened optional union"
assert (
some_of(int | None) is int
), "some of failed to properly handle PEP 604 optional union"
assert (
some_of(dict | list | None) == dict | list
), "some of failed to properly handle PEP 604 flattened optional union"
def test_variable_construction():
from librelane.config import Variable
variable = Variable(
"EXAMPLE",
Optional[int],
description="My Description",
deprecated_names=["OLD_EXAMPLE"],
)
assert variable.optional, ".optional property incorrectly set"
assert variable.some is int, ".some property incorrectly set"
variable_b = Variable(
"EXAMPLE",
Optional[int],
description="My Other Description",
)
assert (
variable == variable_b
), "Variable with different description or deprecated_name didn't match"
variable_c = Variable(
"EXAMPLE",
int | None,
description="My Description",
)
assert (
variable == variable_c
), "Variable with different description or deprecated_name didn't match"
variable_union = Variable(
"UNION_VAR",
Union[int, Dict[str, str]],
description="x",
)
assert (
variable_union.type == Union[int, Dict[str, str]]
), "Union magically switched types"
variable_union_new = Variable(
"UNION_VAR",
int | Dict[str, str],
description="x",
)
assert (
variable_union_new.type == int | Dict[str, str]
), "PEP 604 union didn't match typing union"
assert (
variable_union.type == variable_union_new.type
), "Variable with different union syntax didn't match"
@pytest.fixture
def variable():
from librelane.common import Path
from librelane.config import Variable
return Variable(
"EXAMPLE",
Optional[List[Path]],
description="x",
deprecated_names=["OLD_EXAMPLE"],
)
@pytest.mark.usefixtures("_mock_fs")
def test_compile(variable):
from librelane.common import GenericDict
valid_input = GenericDict({"EXAMPLE": ["/cwd/a", "/cwd/b"]})
warning_list = []
used_name, paths = variable.compile(
valid_input,
warning_list,
)
assert used_name == "EXAMPLE", "valid input returned incorrect used name"
assert paths == [
"/cwd/a",
"/cwd/b",
], "valid input resolved paths incorrectly"
assert len(warning_list) == 0, "valid input generated warning"
def test_compile_dataclass():
from librelane.common import GenericDict
from librelane.config import Variable
@dataclass
class MyClass:
a: int
b: str
variable = Variable("MY_VARIABLE", MyClass, description="x")
_, value = variable.compile(
GenericDict({"MY_VARIABLE": {"a": 4, "b": "potato"}}), []
)
assert value == MyClass(a=4, b="potato"), "Failed to deserialize dataclass"
def test_compile_required():
from librelane.common import GenericDict
from librelane.config import Variable
variable = Variable("MY_VARIABLE", int, description="x")
with pytest.raises(
ValueError,
match=r"Required variable 'MY_VARIABLE' did not get a specified value",
):
variable.compile(GenericDict({}), [])
@pytest.mark.usefixtures("_mock_fs")
def test_compile_deprecated(variable):
from librelane.common import GenericDict
deprecated_valid_input = GenericDict(
{
"OLD_EXAMPLE": [
"/cwd/a",
"/cwd/b",
]
}
)
warning_list = []
used_name, paths = variable.compile(
deprecated_valid_input,
warning_list,
)
assert (
used_name == "OLD_EXAMPLE"
), "deprecated valid input returned incorrect used name"
assert paths == [
"/cwd/a",
"/cwd/b",
], "deprecated valid input returned paths resolved incorrectly"
assert len(warning_list) == 1, "use of deprecated names did not produce a warning"
@pytest.fixture
def test_enum():
from enum import IntEnum
class TestEnum(IntEnum):
AValue = 0
AnotherValue = 1
return TestEnum
def test_compile_validators():
from librelane.config import Variable
from librelane.common import GenericDict
def zero_to_one_validator(variable: Variable, input, _):
if input < 0 or input > 1:
raise ValueError(
f"Value {input} for {variable.name} is invalid: must be between zero and one"
)
return input
def zero_to_one_updater(variable: Variable, input, warning_list_ref):
if input < 0:
warning_list_ref.append(
f"Value for {variable.name} less than 0. Setting to 0."
)
return type(input)(0)
if input > 1:
warning_list_ref.append(
f"Value for {variable.name} higher than 1. Setting to 1."
)
return type(input)(1)
return input
checked = Variable(
"TEST_VARIABLE_VALIDATOR", Decimal, "x", validator=zero_to_one_validator
)
warning_list_ref = []
checked.compile(
GenericDict({"TEST_VARIABLE_VALIDATOR": Decimal("0.5")}), warning_list_ref
)
with pytest.raises(ValueError, match="(must be between zero and one)"):
checked.compile(
GenericDict({"TEST_VARIABLE_VALIDATOR": Decimal("1.1")}), warning_list_ref
)
updated = Variable(
"TEST_VARIABLE_UPDATER", Decimal, "x", validator=zero_to_one_updater
)
_, result = updated.compile(
GenericDict({"TEST_VARIABLE_UPDATER": Decimal("0.5")}), warning_list_ref
)
assert result == Decimal("0.5"), "updater modified valid value"
_, result = updated.compile(
GenericDict({"TEST_VARIABLE_UPDATER": Decimal("24601")}), warning_list_ref
)
assert result == Decimal("1"), "updater did not modify out of scope value"
assert len(warning_list_ref), "invalid updated value did not emit a warning"
@pytest.fixture
def variable_set(variable, test_enum):
from librelane.config import Variable
return [
variable,
Variable(
"LIST_VAR",
List[int],
description="x",
),
Variable(
"TUPLE_2_VAR",
Tuple[int, int],
description="x",
),
Variable(
"TUPLE_3_VAR",
Tuple[int, int, int],
description="x",
),
Variable(
"TUPLE_4_VAR",
Tuple[str, int, int],
description="x",
),
Variable(
"DICT_VAR",
Dict[str, str],
description="x",
),
Variable(
"OTHER_DICT_VAR",
Dict[str, str],
description="x",
),
Variable(
"ANOTHER_DICT_VAR",
Dict[str, str],
description="x",
),
Variable(
"UNION_VAR",
Union[int, Dict[str, str]],
description="x",
),
Variable(
"UNION_VAR_2",
int | Dict[str, str],
description="x",
),
Variable(
"LITERAL_VAR",
Literal["yes"],
description="x",
default="yes",
),
Variable(
"BOOL_VAR",
bool,
description="x",
),
Variable(
"ENUM_VAR",
test_enum,
description="x",
),
Variable(
"NUMBER_VAR",
Decimal,
description="x",
),
]
@pytest.mark.usefixtures("_mock_fs")
def test_compile_invalid(variable_set: list):
from librelane.common import GenericDict
invalid_input = GenericDict(
{
"EXAMPLE": [
"/cwd/a",
"/cwd/c",
],
"LIST_VAR": {},
"TUPLE_2_VAR": [1, {}],
"TUPLE_3_VAR": [1, 4],
"TUPLE_4_VAR": [1, 4],
"DICT_VAR": ["1"],
"OTHER_DICT_VAR": "bad tcl dictionary",
"ANOTHER_DICT_VAR": ["1", "2", "3"],
"UNION_VAR": "lol",
"UNION_VAR_2": "lol",
"LITERAL_VAR": "no",
"BOOL_VAR": "No",
"ENUM_VAR": "NotAValue",
"NUMBER_VAR": "v",
}
)
warning_list = []
for variable in variable_set:
print(f"* Testing {variable.name} ({variable.type})…")
with pytest.raises(
ValueError,
match="(is invalid)|(does not exist)",
):
variable.compile(
invalid_input,
warning_list,
permissive_typing=True,
)
@pytest.mark.usefixtures("_mock_fs")
def test_compile_permissive(variable_set: list, test_enum: Type):
from librelane.common import GenericDict
permissive_valid_input = GenericDict(
{
"EXAMPLE": "/cwd/a /cwd/b",
"LIST_VAR": "4,5,6",
"TUPLE_2_VAR": "1 2",
"TUPLE_3_VAR": "1;2;3",
"TUPLE_4_VAR": "asd;2;3",
"DICT_VAR": "key1 value1 key2 value2",
"OTHER_DICT_VAR": "key1 value1 key2 value2",
"ANOTHER_DICT_VAR": ["key1", "value1", "key2", "value2"],
"UNION_VAR": "4",
"UNION_VAR_2": "4",
"BOOL_VAR": "0",
"ENUM_VAR": "AValue",
"NUMBER_VAR": "90123",
}
)
final = {}
for variable in variable_set:
_, value = variable.compile(
permissive_valid_input,
[],
permissive_typing=True,
)
final[variable.name] = value
assert final == {
"EXAMPLE": ["/cwd/a", "/cwd/b"],
"LIST_VAR": [4, 5, 6],
"TUPLE_2_VAR": (1, 2),
"TUPLE_3_VAR": (1, 2, 3),
"TUPLE_4_VAR": ("asd", 2, 3),
"DICT_VAR": {"key1": "value1", "key2": "value2"},
"OTHER_DICT_VAR": {"key1": "value1", "key2": "value2"},
"ANOTHER_DICT_VAR": {"key1": "value1", "key2": "value2"},
"UNION_VAR": 4,
"UNION_VAR_2": 4,
"BOOL_VAR": False,
"ENUM_VAR": test_enum["AValue"],
"NUMBER_VAR": Decimal("90123"),
"LITERAL_VAR": "yes",
}, "Permissive parsing mode returned an unexpected result"
for variable in variable_set:
if variable.name in ["LITERAL_VAR", "ENUM_VAR"]:
continue
print(f"* Testing {variable.name} ({variable.type})…")
with pytest.raises(ValueError, match="Refusing"):
variable.compile(
permissive_valid_input,
[],
)
c87b16dbfThis file is pinned to the selected release. Download the original to open it in your design tool.