xref: /trunk/test/aoo-test/src/aootest/calcval.py (revision 2c760c133df3253aa7075d23c106a30339fca6f2)
12cb6d6f1SJim Jagielski################################################################
22cb6d6f1SJim Jagielski#
32cb6d6f1SJim Jagielski#  Licensed to the Apache Software Foundation (ASF) under one
42cb6d6f1SJim Jagielski#  or more contributor license agreements.  See the NOTICE file
52cb6d6f1SJim Jagielski#  distributed with this work for additional information
62cb6d6f1SJim Jagielski#  regarding copyright ownership.  The ASF licenses this file
72cb6d6f1SJim Jagielski#  to you under the Apache License, Version 2.0 (the
82cb6d6f1SJim Jagielski#  "License"); you may not use this file except in compliance
92cb6d6f1SJim Jagielski#  with the License.  You may obtain a copy of the License at
102cb6d6f1SJim Jagielski#
112cb6d6f1SJim Jagielski#    http://www.apache.org/licenses/LICENSE-2.0
122cb6d6f1SJim Jagielski#
132cb6d6f1SJim Jagielski#  Unless required by applicable law or agreed to in writing,
142cb6d6f1SJim Jagielski#  software distributed under the License is distributed on an
152cb6d6f1SJim Jagielski#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
162cb6d6f1SJim Jagielski#  KIND, either express or implied.  See the License for the
172cb6d6f1SJim Jagielski#  specific language governing permissions and limitations
182cb6d6f1SJim Jagielski#  under the License.
192cb6d6f1SJim Jagielski
202cb6d6f1SJim Jagielski"""Calc formula cases (data/calcval-cases.tsv) that need no office to read and judge. Shared by the host and the worker.
212cb6d6f1SJim JagielskiPorted from the ODF harness's calcval.py."""
222cb6d6f1SJim Jagielskiimport re
232cb6d6f1SJim Jagielski
242cb6d6f1SJim Jagielskifrom aootest.paths import DATA
252cb6d6f1SJim Jagielski
262cb6d6f1SJim JagielskiCASES = DATA / "calcval-cases.tsv"
272cb6d6f1SJim JagielskiNOT_AVAILABLE = 32767
282cb6d6f1SJim Jagielski# Analysis add-in functions are not known by their English names to setFormula; the cases keep the ODF names and
292cb6d6f1SJim Jagielski# are rewritten to the programmatic names.
30*2c760c13SJim JagielskiADDIN = {"RECEIVED": "getReceived", "BESSELK": "getBesselk", "BESSELY": "getBessely", "CONVERT": "getConvert"}
312cb6d6f1SJim Jagielski
322cb6d6f1SJim Jagielski
332cb6d6f1SJim Jagielskidef parse_mode(mode):
342cb6d6f1SJim Jagielski    """(kind, columns, rows, scratch row) of a case mode: cell, cell@N, array or array:CxR."""
352cb6d6f1SJim Jagielski    m = re.fullmatch(r"cell(?:@(\d+))?", mode)
362cb6d6f1SJim Jagielski    if m:
372cb6d6f1SJim Jagielski        return "cell", 1, 1, int(m.group(1) or 1)
382cb6d6f1SJim Jagielski    m = re.fullmatch(r"array(?::(\d+)x(\d+))?", mode)
392cb6d6f1SJim Jagielski    if m:
402cb6d6f1SJim Jagielski        return "array", int(m.group(1) or 1), int(m.group(2) or 1), 1
412cb6d6f1SJim Jagielski    raise ValueError("bad mode %r" % mode)
422cb6d6f1SJim Jagielski
432cb6d6f1SJim Jagielski
442cb6d6f1SJim Jagielskidef read_cases(path=CASES):
452cb6d6f1SJim Jagielski    cases = []
462cb6d6f1SJim Jagielski    for ln, line in enumerate(open(path, encoding="utf-8"), 1):
472cb6d6f1SJim Jagielski        line = line.rstrip("\n")
482cb6d6f1SJim Jagielski        if not line or line.startswith("#"):
492cb6d6f1SJim Jagielski            continue
502cb6d6f1SJim Jagielski        f = line.split("\t")
512cb6d6f1SJim Jagielski        if len(f) != 5:
522cb6d6f1SJim Jagielski            raise ValueError("%s:%d: expected 5 tab separated columns, got %d" % (path, ln, len(f)))
532cb6d6f1SJim Jagielski        cases.append(dict(zip(("id", "formula", "mode", "expected", "note"), f)))
542cb6d6f1SJim Jagielski    ids = [c["id"] for c in cases]
552cb6d6f1SJim Jagielski    if len(set(ids)) != len(ids):
562cb6d6f1SJim Jagielski        raise ValueError("duplicate case ids in %s" % path)
572cb6d6f1SJim Jagielski    return cases
582cb6d6f1SJim Jagielski
592cb6d6f1SJim Jagielski
602cb6d6f1SJim Jagielskidef api_formula(f):
612cb6d6f1SJim Jagielski    for name, prog in ADDIN.items():
622cb6d6f1SJim Jagielski        f = re.sub(r"(?<![A-Za-z0-9_.])%s\(" % name, "com.sun.star.sheet.addin.Analysis.%s(" % prog, f)
632cb6d6f1SJim Jagielski    return f
642cb6d6f1SJim Jagielski
652cb6d6f1SJim Jagielski
662cb6d6f1SJim Jagielskidef elem_match(exp, cell):
672cb6d6f1SJim Jagielski    """Whether one result cell ({"rt", "err", "str", "val"}) equals the expected element."""
682cb6d6f1SJim Jagielski    if exp == "#N/A":
692cb6d6f1SJim Jagielski        return cell["err"] == NOT_AVAILABLE
702cb6d6f1SJim Jagielski    if exp == "#ERR":
712cb6d6f1SJim Jagielski        return cell["err"] != 0
722cb6d6f1SJim Jagielski    if cell["err"] != 0:
732cb6d6f1SJim Jagielski        return False
742cb6d6f1SJim Jagielski    if exp.startswith("s:"):
752cb6d6f1SJim Jagielski        return cell["rt"] == "TEXT" and cell["str"] == exp[2:]
762cb6d6f1SJim Jagielski    tol = 1e-9
772cb6d6f1SJim Jagielski    if exp.startswith("~"):
782cb6d6f1SJim Jagielski        exp, tol = exp[1:], 1e-6
792cb6d6f1SJim Jagielski    e = float(exp)
802cb6d6f1SJim Jagielski    return cell["rt"] == "VALUE" and isinstance(cell["val"], float) and abs(cell["val"] - e) <= tol * max(1.0, abs(e))
812cb6d6f1SJim Jagielski
822cb6d6f1SJim Jagielski
832cb6d6f1SJim Jagielskidef judge(expected, cells):
842cb6d6f1SJim Jagielski    """record (no expectation), match or mismatch."""
852cb6d6f1SJim Jagielski    if expected == "-":
862cb6d6f1SJim Jagielski        return "record"
872cb6d6f1SJim Jagielski    exps = expected.split("|")
882cb6d6f1SJim Jagielski    if len(exps) != len(cells):
892cb6d6f1SJim Jagielski        return "mismatch"
902cb6d6f1SJim Jagielski    return "match" if all(elem_match(e, c) for e, c in zip(exps, cells)) else "mismatch"
912cb6d6f1SJim Jagielski
922cb6d6f1SJim Jagielski
932cb6d6f1SJim Jagielskidef summary(cells):
942cb6d6f1SJim Jagielski    """The result cells as short text, for failure messages."""
952cb6d6f1SJim Jagielski    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