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