Belle II Software  release-08-01-10
root_returnvalues.py
1 #!/usr/bin/env python3
2 
3 
10 
11 # this is a test executable, not a module so we don't need doxygen warnings
12 # @cond SUPPRESS_DOXYGEN
13 
14 """
15 Check that ROOT returns the correct values (ints, floats) when calling a C++
16 function from python
17 """
18 
19 import sys
20 import ROOT
21 import numpy as np
22 
23 testclass = [
24  "#include <cstdint>",
25  "#include <limits>",
26  "struct ReturnValueTests {"
27 ]
28 
29 results = []
30 # what integer sizes to check
31 integral_types = [8, 16, 32, 64]
32 # which floating types to check
33 floating_types = ["float", "double"]
34 # nan and inf need to be expressed differently so make a mapping
35 floating_expressions = {
36  -np.inf: "-std::numeric_limits<{0}>::infinity()",
37  np.inf: "std::numeric_limits<{0}>::infinity()",
38  np.nan: "std::numeric_limits<{0}>::quiet_NaN()",
39 }
40 
41 for width in integral_types:
42  # check signed types, we want min, max -1, 0 and 1
43  dtype = np.dtype("int%d" % width)
44  for i, value in enumerate([-2**(width - 1), -1, 0, 1, 2**(width - 1) - 1]):
45  # create a unique member name
46  func_name = f"int{width}test{i}"
47  # do we need a prefix to the literal?
48  prefix = "ull" if width > 32 else ""
49  # append it to the test class
50  testclass.append(f"int{width}_t {func_name}() const {{ return {value}{prefix}; }}")
51  testclass.append("int{0}_t& ref_{1}() {{ static int{0}_t val = {2}{3}; return val; }}".format(
52  width, func_name, value, prefix))
53  # and remember name and result for checking
54  results.append([func_name, value, dtype])
55  results.append(['ref_' + func_name, value, dtype])
56 
57  # check unsigned types, just 0, 1, and max
58  dtype = np.dtype("uint%d" % width)
59  for i, value in enumerate([0, 1, 2**(width) - 1]):
60  # create a unique member name
61  func_name = f"uint{width}test{i}"
62  # do we need a prefix to the literal?
63  prefix = "ull" if width > 32 else ""
64  # append it to the test class
65  testclass.append(f"uint{width}_t {func_name}() const {{ return {value}{prefix}; }}")
66  testclass.append(f"uint{width}_t& ref_{func_name}() {{ static uint{width}_t val = {value}{prefix}; return val; }}")
67  # and remember name and result for checking
68  results.append([func_name, value, dtype])
69  results.append(['ref_' + func_name, value, dtype])
70 
71 # now add floating types
72 for t in floating_types:
73  # and exploit that numpy offers a numeric_limits equivalent
74  info = np.finfo(t[0])
75 
76  # check some values
77  for i, value in enumerate([-np.inf, info.min, -1, 0, info.tiny, info.eps, 1, info.max, np.inf, np.nan]):
78  func_name = f"{t}test{i}"
79  # see if we can just have the literal or if we need a mapping
80  expression = repr(value) if value not in floating_expressions else floating_expressions[value].format(t)
81  testclass.append(f"{t} {func_name}() const {{ return {expression}; }}")
82  testclass.append("{0}& ref_{1}() {{ static {0} val = {2}; return val; }}".format(t, func_name, expression))
83  results.append([func_name, value, info.dtype])
84  results.append(['ref_' + func_name, value, info.dtype])
85 
86 
87 # compile the test class
88 testclass.append("};")
89 ROOT.gROOT.ProcessLine("\n".join(testclass))
90 # get an instance
91 tests = ROOT.ReturnValueTests()
92 # and do all the checks
93 failures = 0
94 for func, value, dtype in results:
95  ret = getattr(tests, func)()
96  # char is odd and returns strings instead of int. The simple way of using
97  # ord(string) will loose the sign so we have to convert it to the correct
98  # type using numpy but for that we have to convert the string to 8bit.
99  if isinstance(ret, str):
100  ret = np.fromstring(ret.encode("latin1"), dtype)[0]
101 
102  # print the test
103  print(f"check {func}(): {value!r} == {ret!r}: ", end="")
104  # nan needs special care because it cannot be compared to itself
105  if np.isnan(value):
106  passed = np.isnan(ret)
107  else:
108  passed = (ret == value)
109 
110  print("\033[32mOK\033[0m" if passed else "\033[31mFAIL\033[0m")
111  if not passed:
112  failures += 1
113 
114 sys.exit(failures)
115 
116 # @endcond