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