/ test_libs / pyspec / eth2spec / test / utils.py
utils.py
 1  from typing import Dict, Any, Callable, Iterable
 2  from eth2spec.debug.encode import encode
 3  
 4  
 5  def spectest(description: str = None):
 6      def runner(fn):
 7          # this wraps the function, to hide that the function actually is yielding data, instead of returning once.
 8          def entry(*args, **kw):
 9              # check generator mode, may be None/else.
10              # "pop" removes it, so it is not passed to the inner function.
11              if kw.pop('generator_mode', False) is True:
12                  out = {}
13                  if description is None:
14                      # fall back on function name for test description
15                      name = fn.__name__
16                      if name.startswith('test_'):
17                          name = name[5:]
18                      out['description'] = name
19                  else:
20                      # description can be explicit
21                      out['description'] = description
22                  has_contents = False
23                  # put all generated data into a dict.
24                  for data in fn(*args, **kw):
25                      has_contents = True
26                      # If there is a type argument, encode it as that type.
27                      if len(data) == 3:
28                          (key, value, typ) = data
29                          out[key] = encode(value, typ)
30                      else:
31                          # Otherwise, try to infer the type, but keep it as-is if it's not a SSZ container.
32                          (key, value) = data
33                          if hasattr(value.__class__, 'fields'):
34                              out[key] = encode(value, value.__class__)
35                          else:
36                              out[key] = value
37                  if has_contents:
38                      return out
39                  else:
40                      return None
41              else:
42                  # just complete the function, ignore all yielded data, we are not using it
43                  for _ in fn(*args, **kw):
44                      continue
45                  return None
46          return entry
47      return runner
48  
49  
50  def with_tags(tags: Dict[str, Any]):
51      """
52      Decorator factory, adds tags (key, value) pairs to the output of the function.
53      Useful to build test-vector annotations with.
54      This decorator is applied after the ``spectest`` decorator is applied.
55      :param tags: dict of tags
56      :return: Decorator.
57      """
58      def runner(fn):
59          def entry(*args, **kw):
60              fn_out = fn(*args, **kw)
61              # do not add tags if the function is not returning a dict at all (i.e. not in generator mode)
62              if fn_out is None:
63                  return None
64              return {**tags, **fn_out}
65          return entry
66      return runner
67  
68  
69  def with_args(create_args: Callable[[], Iterable[Any]]):
70      """
71      Decorator factory, adds given extra arguments to the decorated function.
72      :param create_args: function to create arguments with.
73      :return: Decorator.
74      """
75      def runner(fn):
76          # this wraps the function, to hide that the function actually yielding data.
77          def entry(*args, **kw):
78              return fn(*(list(create_args()) + list(args)), **kw)
79          return entry
80      return runner