xref: /trunk/test/aoo-test/src/aootest/calcval.py (revision 2c760c133df3253aa7075d23c106a30339fca6f2)
1################################################################
2#
3#  Licensed to the Apache Software Foundation (ASF) under one
4#  or more contributor license agreements.  See the NOTICE file
5#  distributed with this work for additional information
6#  regarding copyright ownership.  The ASF licenses this file
7#  to you under the Apache License, Version 2.0 (the
8#  "License"); you may not use this file except in compliance
9#  with the License.  You may obtain a copy of the License at
10#
11#    http://www.apache.org/licenses/LICENSE-2.0
12#
13#  Unless required by applicable law or agreed to in writing,
14#  software distributed under the License is distributed on an
15#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16#  KIND, either express or implied.  See the License for the
17#  specific language governing permissions and limitations
18#  under the License.
19
20"""Calc formula cases (data/calcval-cases.tsv) that need no office to read and judge. Shared by the host and the worker.
21Ported from the ODF harness's calcval.py."""
22import re
23
24from aootest.paths import DATA
25
26CASES = DATA / "calcval-cases.tsv"
27NOT_AVAILABLE = 32767
28# Analysis add-in functions are not known by their English names to setFormula; the cases keep the ODF names and
29# are rewritten to the programmatic names.
30ADDIN = {"RECEIVED": "getReceived", "BESSELK": "getBesselk", "BESSELY": "getBessely", "CONVERT": "getConvert"}
31
32
33def parse_mode(mode):
34    """(kind, columns, rows, scratch row) of a case mode: cell, cell@N, array or array:CxR."""
35    m = re.fullmatch(r"cell(?:@(\d+))?", mode)
36    if m:
37        return "cell", 1, 1, int(m.group(1) or 1)
38    m = re.fullmatch(r"array(?::(\d+)x(\d+))?", mode)
39    if m:
40        return "array", int(m.group(1) or 1), int(m.group(2) or 1), 1
41    raise ValueError("bad mode %r" % mode)
42
43
44def read_cases(path=CASES):
45    cases = []
46    for ln, line in enumerate(open(path, encoding="utf-8"), 1):
47        line = line.rstrip("\n")
48        if not line or line.startswith("#"):
49            continue
50        f = line.split("\t")
51        if len(f) != 5:
52            raise ValueError("%s:%d: expected 5 tab separated columns, got %d" % (path, ln, len(f)))
53        cases.append(dict(zip(("id", "formula", "mode", "expected", "note"), f)))
54    ids = [c["id"] for c in cases]
55    if len(set(ids)) != len(ids):
56        raise ValueError("duplicate case ids in %s" % path)
57    return cases
58
59
60def api_formula(f):
61    for name, prog in ADDIN.items():
62        f = re.sub(r"(?<![A-Za-z0-9_.])%s\(" % name, "com.sun.star.sheet.addin.Analysis.%s(" % prog, f)
63    return f
64
65
66def elem_match(exp, cell):
67    """Whether one result cell ({"rt", "err", "str", "val"}) equals the expected element."""
68    if exp == "#N/A":
69        return cell["err"] == NOT_AVAILABLE
70    if exp == "#ERR":
71        return cell["err"] != 0
72    if cell["err"] != 0:
73        return False
74    if exp.startswith("s:"):
75        return cell["rt"] == "TEXT" and cell["str"] == exp[2:]
76    tol = 1e-9
77    if exp.startswith("~"):
78        exp, tol = exp[1:], 1e-6
79    e = float(exp)
80    return cell["rt"] == "VALUE" and isinstance(cell["val"], float) and abs(cell["val"] - e) <= tol * max(1.0, abs(e))
81
82
83def judge(expected, cells):
84    """record (no expectation), match or mismatch."""
85    if expected == "-":
86        return "record"
87    exps = expected.split("|")
88    if len(exps) != len(cells):
89        return "mismatch"
90    return "match" if all(elem_match(e, c) for e, c in zip(exps, cells)) else "mismatch"
91
92
93def summary(cells):
94    """The result cells as short text, for failure messages."""
95    return " | ".join("err=%d" % c["err"] if c["err"] else ("%r" % c["val"] if c["rt"] == "VALUE" else "%r" % c["str"]) for c in cells)
96