code: def makeTester(
      name,
      op,
      expected,
      checks=None,
      good=None,
      bad_build=None,
      bad_runtime=None,
      grad=None,
      mode=None,
      grad_rtol=None,
      eps=1e-10,
      skip=False,
      test_memmap=True,
      check_name=False,
      grad_eps=None,
  ):
      """
      Parameters
      ----------
      check_name
          Use only for testers that aren't in PyTensor.
      """
      if checks is None:
          checks = {}
      if good is None:
          good = {}
      if bad_build is None:
          bad_build = {}
      if bad_runtime is None:
          bad_runtime = {}
      if grad is None:
          grad = {}
      if grad is True:
          grad = good
  
      _op, _expected, _checks, _good = op, expected, checks, good
      _bad_build, _bad_runtime, _grad = bad_build, bad_runtime, grad
      _mode, _grad_rtol, _eps, skip_ = mode, grad_rtol, eps, skip
      _test_memmap = test_memmap
      _check_name = check_name
      _grad_eps = grad_eps
  
      class Checker:
          op = staticmethod(_op)
          expected = staticmethod(_expected)
          checks = _checks
          check_name = _check_name
          good = _good
          bad_build = _bad_build
          bad_runtime = _bad_runtime
          grad = _grad
          mode = _mode
          skip = skip_
          test_memmap = _test_memmap
  
          def setup_method(self):
              # Verify that the test's name is correctly set.
              # Some tests reuse it outside this module.
              if self.check_name:
                  eval(
                      self.__class__.__module__
                      + "."
                      + self.__class__.__name__
                  )
  
              # We keep a list of temporary files created in add_memmap_values,
              # to remove them at the end of the test.
              self.tmp_files = []
  
          def add_memmap_values(self, val_dict):
              # If test_memmap is True, we create a temporary file
              # containing a copy of the data passed in the "val_dict" dict,
              # then open it as a memmapped array, and we can use the result as a
              # new test value.
              if not self.test_memmap:
                  return val_dict
  
              # Copy dict before modifying them
              val_dict = val_dict.copy()
  
              # Note that we sort items in the dictionary to ensure tests are
              # deterministic (since the loop below will break on the first valid
              # item that can be memmapped).
              for k, v in sorted(val_dict.items()):
                  new_k = "_".join((k, "memmap"))
                  if new_k in val_dict:
                      # A corresponding key was already provided
                      break
  
                  new_v = []
                  for inp in v:
                      if isinstance(inp, np.ndarray) and inp.size > 0:
                          f, fname = mkstemp()
                          self.tmp_files.append((f, fname))
                          new_inp = np.memmap(
                              fname,
                              dtype=inp.dtype,
                              mode="w+",
                              shape=inp.shape,
                          )
                          new_inp[...] = inp[...]
                          new_v.append(new_inp)
                      else:
                          new_v.append(inp)
                  val_dict[new_k] = new_v
  
                  # We only need one value, no need to copy all of them
                  break
              return val_dict
  
          def teardown_method(self):
              # This is to avoid a problem with deleting memmap files on windows.
              import gc
  
              gc.collect()
              for f, fname in self.tmp_files:
                  os.close(f)
                  os.remove(fname)
  
          @pytest.mark.skipif(skip, reason="Skipped")
          def test_good(self):
              good = self.add_memmap_values(self.good)
  
              for testname, inputs in good.items():
                  inputs = [copy(input) for input in inputs]
                  inputrs = [
                      TensorType(
                          dtype=input.dtype,
                          shape=tuple(
                              1 if shape_elem == 1 else None
                              for shape_elem in input.shape
                          ),
                      )()
                      for input in inputs
                  ]
                  try:
                      node = safe_make_node(self.op, *inputrs)
                  except Exception as exc:
                      err_msg = (
                          "Test %s::%s: Error occurred while"
                          " making a node with inputs %s"
                      ) % (self.op, testname, inputs)
                      exc.args += (err_msg,)
                      raise
  
                  try:
                      f = inplace_func(
                          inputrs,
                          node.outputs,
                          mode=mode,
                          name="test_good",
                      )
                  except Exception as exc:
                      err_msg = (
                          "Test %s::%s: Error occurred while"
                          " trying to make a Function"
                      ) % (self.op, testname)
                      exc.args += (err_msg,)
                      raise
                  if (
                      isinstance(self.expected, dict)
                      and testname in self.expected
                  ):
                      expecteds = self.expected[testname]
                      # with numpy version, when we print a number and read it
                      # back, we don't get exactly the same result, so we accept
                      # rounding error in that case.
                      eps = 5e-9
                  else:
                      expecteds = self.expected(*inputs)
                      eps = 1e-10
  
                  if any(
                      i.dtype in ("float32", "int8", "uint8", "uint16")
                      for i in inputs
                  ):
                      eps = 1e-6
                  eps = max(eps, _eps)
  
                  try:
                      variables = f(*inputs)
                  except Exception as exc:
                      err_msg = (
                          "Test %s::%s: Error occurred while calling"
                          " the Function on the inputs %s"
                      ) % (self.op, testname, inputs)
                      exc.args += (err_msg,)
                      raise
  
                  if not isinstance(expecteds, (list, tuple)):
                      expecteds = (expecteds,)
  
                  for i, (variable, expected) in enumerate(
                      zip(variables, expecteds)
                  ):
                      condition = (
                          variable.dtype != expected.dtype
                          or variable.shape != expected.shape
                          or not np.allclose(
                              variable, expected, atol=eps, rtol=eps
                          )
                      )
                      assert not condition, (
                          "Test %s::%s: Output %s gave the wrong"
                          " value. With inputs %s, expected %s (dtype %s),"
                          " got %s (dtype %s). eps=%f"
                          " np.allclose returns %s %s"
                      ) % (
                          self.op,
                          testname,
                          i,
                          inputs,
                          expected,
                          expected.dtype,
                          variable,
                          variable.dtype,
                          eps,
                          np.allclose(
                              variable, expected, atol=eps, rtol=eps
                          ),
                          np.allclose(variable, expected),
                      )
  
                  for description, check in self.checks.items():
                      assert check(inputs, variables), (
                          "Test %s::%s: Failed check: %s (inputs"
                          " were %s, outputs were %s)"
                      ) % (
                          self.op,
                          testname,
                          description,
                          inputs,
                          variables,
                      )
  
          @pytest.mark.skipif(skip, reason="Skipped")
          def test_bad_build(self):
              for testname, inputs in self.bad_build.items():
                  inputs = [copy(input) for input in inputs]
                  inputrs = [shared(input) for input in inputs]
                  with pytest.raises(Exception):
                      safe_make_node(self.op, *inputrs)
                  # The old error string was ("Test %s::%s: %s was successfully
                  # instantiated on the following bad inputs: %s"
                  # % (self.op, testname, node, inputs))
  
          @config.change_flags(compute_test_value="off")
          @pytest.mark.skipif(skip, reason="Skipped")
          def test_bad_runtime(self):
              for testname, inputs in self.bad_runtime.items():
                  inputrs = [shared(input) for input in inputs]
                  try:
                      node = safe_make_node(self.op, *inputrs)
                  except Exception as exc:
                      err_msg = (
                          "Test %s::%s: Error occurred while trying"
                          " to make a node with inputs %s"
                      ) % (self.op, testname, inputs)
                      exc.args += (err_msg,)
                      raise
  
                  try:
                      f = inplace_func(
                          [],
                          node.outputs,
                          mode=mode,
                          name="test_bad_runtime",
                      )
                  except Exception as exc:
                      err_msg = (
                          "Test %s::%s: Error occurred while trying"
                          " to make a Function"
                      ) % (self.op, testname)
                      exc.args += (err_msg,)
                      raise
  
                  # Add tester return a ValueError. Should we catch only this
                  # one?
                  # TODO: test that only this one is raised and catch only this
                  # one or the subset that get raised.
                  with pytest.raises(Exception):
                      f([])
  
          @pytest.mark.skipif(skip, reason="Skipped")
          def test_grad(self):
              for testname, inputs in self.grad.items():
                  inputs = [copy(input) for input in inputs]
                  try:
                      utt.verify_grad(
                          self.op,
                          inputs,
                          mode=self.mode,
                          rel_tol=_grad_rtol,
                          eps=_grad_eps,
                      )
                  except Exception as exc:
                      err_msg = (
                          "Test %s::%s: Error occurred while"
                          " computing the gradient on the following"
                          " inputs: %s"
                      ) % (self.op, testname, inputs)
                      exc.args += (err_msg,)
                      raise
  
          @pytest.mark.skipif(skip, reason="Skipped")
          def test_grad_none(self):
              # Check that None is never returned as input gradient
              # when calling self.op.grad
              # We use all values in self.good because this has to be true
              # whether or not the values work for utt.verify_grad.
              if not hasattr(self.op, "grad"):
                  # This is not actually an Op
                  return
  
              for testname, inputs in self.good.items():
                  inputs = [copy(input) for input in inputs]
                  inputrs = [
                      TensorType(
                          dtype=input.dtype,
                          shape=tuple(
                              1 if shape_elem == 1 else None
                              for shape_elem in input.shape
                          ),
                      )()
                      for input in inputs
                  ]
  
                  if (
                      isinstance(self.expected, dict)
                      and testname in self.expected
                  ):
                      expecteds = self.expected[testname]
                      # with numpy version, when we print a number and read it
                      # back, we don't get exactly the same result, so we accept
                      # rounding error in that case.
                  else:
                      expecteds = self.expected(*inputs)
                  if not isinstance(expecteds, (list, tuple)):
                      expecteds = (expecteds,)
  
                  out_grad_vars = []
                  for out in expecteds:
                      if str(out.dtype) in discrete_dtypes:
                          dtype = config.floatX
                      else:
                          dtype = str(out.dtype)
                      out_shape = tuple(
                          1 if shape_elem == 1 else None
                          for shape_elem in out.shape
                      )
                      var = TensorType(dtype=dtype, shape=out_shape)()
                      out_grad_vars.append(var)
  
                  try:
                      in_grad_vars = self.op.grad(
                          inputrs, out_grad_vars
                      )
                  except (MethodNotDefined, NotImplementedError):
                      pass
                  else:
                      assert None not in in_grad_vars
  
      Checker.__name__ = name
      if hasattr(Checker, "__qualname__"):
          Checker.__qualname__ = name
      return Checker
  
imports: import os
  from copy import copy
  from itertools import combinations
  from tempfile import mkstemp
  import numpy
  import pytest
  import pytensor
  from pytensor import function, shared
  from pytensor.compile.mode import get_default_mode
  from pytensor.configdefaults import config
  from pytensor.graph.utils import MethodNotDefined
  from pytensor.misc.safe_asarray import _asarray
  from pytensor.tensor.type import TensorType, complex_dtypes, discrete_dtypes, float_dtypes
  from tests import unittest_tools
label_desc: Use image node_id 24 for calling a global function with example usage: makeTester(name, op, expected, checks, good, bad_build, bad_runtime, grad, mode, grad_rtol, eps, skip, test_memmap, check_name, grad_eps) and returns: Checker, val_dict, val_dict