"""
Integration coverage for the KERNEL **class-form** golden dispatch —
the refactor's headline feature. The full wiring under test is::
__generate_golden
-> get_plugin_function(op, "golden", "kernel") -> returns a CLASS
-> __invoke_golden: isinstance(golden_func, type) is True
-> __invoke_class
pool = {**inputs_named(from OpInfoKeeper), **context.attributes}
cls.__init__ is object.__init__ ? cls() : bind_by_name(__init__, pool) -> cls(*a, **k)
bind_by_name(inst.__call__, pool) -> inst(*ca, **ck)
-> numeric numpy arrays
`bind_by_name` itself is unit-tested elsewhere; these tests prove the
*integration* (context -> OpInfoKeeper pool build -> __init__ bind ->
__call__ bind -> result) that was previously uncovered. Two shapes:
1. **Partial split with custom ``__init__``** (``_NegScale``): the config
attribute flows through ``__init__`` and the input through ``__call__``.
2. **No custom ``__init__``** (``_Double``): exercises the
``cls.__init__ is object.__init__`` guard so ``cls()`` is called.
"""
import numpy as np
import pytest
from unittest.mock import patch, MagicMock
from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp
from ttk.core_modules.npu.op import output_generation as _mod
_generate_golden = getattr(_mod, '__generate_golden')
class _NegScale:
"""Partial split: ``__init__`` takes a config attr, ``__call__`` takes the input."""
def __init__(self, *, scale):
self.scale = scale
def __call__(self, x):
return [x * self.scale]
class _Double:
"""No custom ``__init__`` — must hit the ``cls()`` guard in ``__invoke_class``."""
def __call__(self, x):
return [x * 2]
def _make_testcase(op_name, input_shapes, input_dtypes, output_shapes, output_dtypes,
attributes):
case = TestcaseOp()
case.testcase_name = f"test_{op_name}_class_golden"
case.op_name = op_name
case.input_shapes = input_shapes
case.input_dtypes = input_dtypes
case.output_shapes = output_shapes
case.output_dtypes = output_dtypes
case.input_ori_shapes = input_shapes
case.output_ori_shapes = output_shapes
n_in = len(input_shapes)
n_out = len(output_shapes or ())
case.input_formats = ("ND",) * n_in
case.input_ori_formats = ("ND",) * n_in
case.output_formats = ("ND",) * n_out
case.output_ori_formats = ("ND",) * n_out
case.input_data_ranges = (None,) * n_in
case.attributes = attributes
case.input_arrays = tuple(np.ones(s, dtype=d) for s, d in zip(input_shapes, input_dtypes))
case.original_input_arrays = None
return case
def _mock_switches():
sw = MagicMock()
sw.dev_plat = "Ascend910B2"
sw.short_soc_version = "Ascend910B"
sw.golden_mode = "Enable"
sw.plugin_path = None
sw.overflow_mode = 0
return sw
def _mock_op_info(input_names):
"""OpInfoKeeper mock whose info_of() yields inputs named per ``input_names``."""
mock = MagicMock()
mock.return_value.info_of.return_value = {"inputs": [{"name": n} for n in input_names]}
return mock
@pytest.fixture(autouse=True)
def _mock_env(monkeypatch):
monkeypatch.delenv("ASCEND_HOME_PATH", raising=False)
monkeypatch.delenv("ASCEND_TOOLKIT_HOME", raising=False)
monkeypatch.delenv("ASCEND_OPP_PATH", raising=False)
@patch('ttk.core_modules.npu.op.output_generation.OpInfoKeeper')
@patch('ttk.core_modules.npu.op.output_generation.get_global_storage')
@patch('ttk.core_modules.npu.op.output_generation.get_plugin_function')
class TestKernelClassGoldenDispatch:
"""class-form golden (isinstance(golden_func, type)) → __invoke_class end-to-end."""
def test_partial_split_init_takes_attr_call_takes_input(self, mock_get_plugin, mock_sw,
mock_op_info):
"""``_NegScale(scale=3)`` instantiated via ``__init__``; ``__call__(x)`` returns ``x*3``."""
mock_get_plugin.return_value = _NegScale
mock_sw.return_value = _mock_switches()
mock_op_info.return_value.info_of.return_value = {"inputs": [{"name": "x"}]}
inp = np.array([1.0, 2.0, 3.0], dtype="float32")
case = _make_testcase(op_name="neg_scale",
input_shapes=((3,),), input_dtypes=("float32",),
output_shapes=((3,),), output_dtypes=("float32",),
attributes={"scale": 3})
case.input_arrays = (inp,)
golden = _generate_golden(case, ["float32"])
assert len(golden) >= 1
assert isinstance(golden[0], np.ndarray), \
f"expected ndarray, got sentinel/string: {golden[0]!r}"
np.testing.assert_array_equal(golden[0], np.array([3.0, 6.0, 9.0], dtype="float32"))
mock_get_plugin.assert_called_once()
def test_no_custom_init_hits_cls_guard(self, mock_get_plugin, mock_sw, mock_op_info):
"""``_Double`` has no ``__init__`` → ``cls()`` guard path; ``__call__(x)`` → ``x*2``."""
mock_get_plugin.return_value = _Double
mock_sw.return_value = _mock_switches()
mock_op_info.return_value.info_of.return_value = {"inputs": [{"name": "x"}]}
inp = np.array([1.0, 2.0, 3.0], dtype="float32")
case = _make_testcase(op_name="double",
input_shapes=((3,),), input_dtypes=("float32",),
output_shapes=((3,),), output_dtypes=("float32",),
attributes={})
case.input_arrays = (inp,)
golden = _generate_golden(case, ["float32"])
assert len(golden) >= 1
assert isinstance(golden[0], np.ndarray), \
f"expected ndarray, got sentinel/string: {golden[0]!r}"
np.testing.assert_array_equal(golden[0], np.array([2.0, 4.0, 6.0], dtype="float32"))
mock_get_plugin.assert_called_once()
def test_dispatch_routes_class_to_invoke_class_not_callable_path(self, mock_get_plugin,
mock_sw, mock_op_info):
"""A class is a ``Callable`` but MUST route to ``__invoke_class`` (not the custom-callable
path that would do ``golden_func(*input_arrays)``). Guards the ``isinstance(type)`` check
ordering in ``__invoke_golden``: if a class leaked to the custom branch it would be called
as ``_NegScale(array)`` → TypeError → ``['GOLDEN_FAILURE']``."""
mock_get_plugin.return_value = _NegScale
mock_sw.return_value = _mock_switches()
mock_op_info.return_value.info_of.return_value = {"inputs": [{"name": "x"}]}
inp = np.array([1.0, 2.0, 3.0], dtype="float32")
case = _make_testcase(op_name="neg_scale",
input_shapes=((3,),), input_dtypes=("float32",),
output_shapes=((3,),), output_dtypes=("float32",),
attributes={"scale": 5})
case.input_arrays = (inp,)
golden = _generate_golden(case, ["float32"])
assert isinstance(golden[0], np.ndarray), \
f"class golden must hit __invoke_class, not the custom-callable branch: {golden[0]!r}"
np.testing.assert_array_equal(golden[0], np.array([5.0, 10.0, 15.0], dtype="float32"))