blob: 925c29282b18bf7a2516bae564b9fc4793e51160 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
""" Hexagon pytest utility functions """
from typing import List, Optional, Union
import collections
import numpy as np
def get_test_id(*test_params, test_param_descs: List[Optional[str]] = None) -> str:
"""
An opinionated alternative to pytest's default algorithm for generating a
test's ID string. Intended to make it easier for human readers to
interpret the test IDs.
'test_params': The sequence of pytest parameter values supplied to some unit
test.
'test_param_descs': An (optional) means to provide additional text for some/all of the
paramuments in 'test_params'.
If provided, then len(test_params) must equal len(test_param_descs).
Each element test_param_descs that is a non-empty string will be used
in some sensible way in this function's returned string.
"""
assert len(test_params) > 0
if test_param_descs is None:
test_param_descs = [None] * len(test_params)
else:
assert len(test_param_descs) == len(test_params)
def get_single_param_chunk(param_val, param_desc: Optional[str]):
if isinstance(param_val, list):
# Like str(list), but avoid the whitespace padding.
val_str = "[" + ",".join(str(x) for x in param_val) + "]"
need_prefix_separator = False
elif isinstance(param_val, bool):
if param_val:
val_str = "T"
else:
val_str = "F"
need_prefix_separator = True
elif isinstance(param_val, TensorContentConstant):
val_str = f"const[{param_val.elem_value}]"
need_prefix_separator = True
elif isinstance(param_val, TensorContentDtypeMin):
val_str = "min"
need_prefix_separator = True
elif isinstance(param_val, TensorContentDtypeMax):
val_str = "max"
need_prefix_separator = True
elif isinstance(param_val, TensorContentRandom):
val_str = "random"
need_prefix_separator = True
elif isinstance(param_val, TensorContentSequentialCOrder):
val_str = f"seqC[start:{param_val.start_value},inc:{param_val.increment}]"
need_prefix_separator = True
else:
val_str = str(param_val)
need_prefix_separator = True
if param_desc and need_prefix_separator:
return f"{param_desc}:{val_str}"
elif param_desc and not need_prefix_separator:
return f"{param_desc}{val_str}"
else:
return val_str
chunks = [
get_single_param_chunk(param_val, param_desc)
for param_val, param_desc in zip(test_params, test_param_descs)
]
return "-".join(chunks)
def get_multitest_ids(
multitest_params_list: List[List], param_descs: Optional[List[Optional[str]]]
) -> List[str]:
"""
A convenience function for classes that use both 'tvm.testing.parameters' and 'get_test_id'.
This function provides a workaround for a specific quirk in Python, where list-comprehension
can't necessarily access the value of another class-variable, discused here:
https://stackoverflow.com/q/13905741
"""
return [
get_test_id(*single_test_param_list, test_param_descs=param_descs)
for single_test_param_list in multitest_params_list
]
def get_numpy_dtype_info(dtype) -> Union[np.finfo, np.iinfo]:
"""
Return an appropriate 'np.iinfo' or 'np.finfo' object corresponding to
the specified Numpy dtype.
'dtype' must be a value that 'numpy.dtype(...)' can handle.
"""
np_dtype = np.dtype(dtype)
kind = np_dtype.kind
if kind == "f":
return np.finfo(np_dtype)
elif kind == "i":
return np.iinfo(np_dtype)
else:
raise TypeError(f"dtype ({dtype}) must indicate some floating-point or integral data type")
TensorContentConstant = collections.namedtuple("TensorContentConstant", ["elem_value"])
TensorContentSequentialCOrder = collections.namedtuple(
"TensorContentSequentialCOrder", ["start_value", "increment"]
)
TensorContentRandom = collections.namedtuple("TensorContentRandom", [])
TensorContentDtypeMin = collections.namedtuple("TensorContentDtypeMin", [])
TensorContentDtypeMax = collections.namedtuple("TensorContentDtypeMax", [])
def create_populated_numpy_tensor(
input_shape: Union[list, tuple], dtype: str, input_tensor_populator
) -> np.ndarray:
"""
Create a numpy tensor with the specified shape, dtype, and content.
"""
itp = input_tensor_populator # just for brevity
if isinstance(itp, TensorContentConstant):
return np.full(tuple(input_shape), itp.elem_value, dtype=dtype)
elif isinstance(itp, TensorContentDtypeMin):
info = get_numpy_dtype_info(dtype)
return np.full(tuple(input_shape), info.min, dtype=dtype)
elif isinstance(itp, TensorContentDtypeMax):
info = get_numpy_dtype_info(dtype)
return np.full(tuple(input_shape), info.max, dtype=dtype)
elif isinstance(itp, TensorContentRandom):
return np.random.random(input_shape).astype(dtype)
elif isinstance(itp, TensorContentSequentialCOrder):
a = np.empty(tuple(input_shape), dtype)
with np.nditer(a, op_flags=["writeonly"], order="C") as iterator:
next_elem_val = itp.start_value
for elem in iterator:
elem[...] = next_elem_val
next_elem_val += itp.increment
return a
else:
raise ValueError(f"Unexpected input_tensor_populator type: {type(itp)}")