| 1 | package xlsx_test |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | "strings" |
| 8 | "testing" |
| 9 | ) |
| 10 | |
| 11 | // oracleCorpusPath and oracleExclusionsPath point at the SAME committed |
| 12 | // files internal/sheets/calc/corpus_test.go reads (corpusPath, |
| 13 | // exclusionsPath there) — one corpus, cross-checked from two packages. |
| 14 | // This package cannot import calc's _test.go types (Go test files are |
| 15 | // not part of a package's importable surface), so the JSON shape is |
| 16 | // duplicated here rather than shared; keep it in sync with corpus_test.go |
| 17 | // if formulas.json's wire format ever changes. |
| 18 | const ( |
| 19 | oracleCorpusPath = "../calc/testdata/corpus/formulas.json" |
| 20 | oracleExclusionsPath = "../calc/testdata/corpus/volatile-excluded.txt" |
| 21 | ) |
| 22 | |
| 23 | // oracleCorpusValue mirrors calc's corpusValue (corpus_test.go): only |
| 24 | // the field named by Kind is meaningful. |
| 25 | type oracleCorpusValue struct { |
| 26 | Kind string `json:"kind"` |
| 27 | Num float64 `json:"num,omitempty"` |
| 28 | Str string `json:"str,omitempty"` |
| 29 | Bool bool `json:"bool,omitempty"` |
| 30 | Err string `json:"err,omitempty"` |
| 31 | } |
| 32 | |
| 33 | // oracleCorpusEntry mirrors calc's corpusEntry. Want is carried through |
| 34 | // for context in failure/log messages, but this harness's actual |
| 35 | // comparison is our engine's live Eval output against excelize's live |
| 36 | // CalcCellValue output — Want plays no part in that comparison, unlike |
| 37 | // TestAgainstReferenceCorpus in the calc package, which checks Eval |
| 38 | // against Want directly. |
| 39 | type oracleCorpusEntry struct { |
| 40 | Function string `json:"function"` |
| 41 | Expr string `json:"expr"` |
| 42 | Want oracleCorpusValue `json:"want"` |
| 43 | } |
| 44 | |
| 45 | type oracleCorpusFile struct { |
| 46 | Note string `json:"note"` |
| 47 | Entries []oracleCorpusEntry `json:"entries"` |
| 48 | } |
| 49 | |
| 50 | // loadOracleCorpus reads and parses the same formulas.json |
| 51 | // TestAgainstReferenceCorpus (calc package) uses. |
| 52 | func loadOracleCorpus(t *testing.T) []oracleCorpusEntry { |
| 53 | t.Helper() |
| 54 | data, err := os.ReadFile(filepath.FromSlash(oracleCorpusPath)) |
| 55 | if err != nil { |
| 56 | t.Fatalf("loadOracleCorpus: %v", err) |
| 57 | } |
| 58 | var cf oracleCorpusFile |
| 59 | if err := json.Unmarshal(data, &cf); err != nil { |
| 60 | t.Fatalf("loadOracleCorpus: %v", err) |
| 61 | } |
| 62 | if len(cf.Entries) == 0 { |
| 63 | t.Fatal("loadOracleCorpus: corpus has no entries") |
| 64 | } |
| 65 | return cf.Entries |
| 66 | } |
| 67 | |
| 68 | // loadOracleExclusions parses the same volatile-excluded.txt |
| 69 | // TestAgainstReferenceCorpus honors, for the same reason: NOW/TODAY |
| 70 | // return a different answer on every evaluation (calc's env.Now() is |
| 71 | // pinned per-pass to a fixed instant; excelize's NOW()/TODAY() read the |
| 72 | // real wall clock), so no captured pair of answers from the two engines |
| 73 | // can ever be expected to agree, independent of whether either engine |
| 74 | // is "right." Skipping here mirrors calc's own reasoning, it does not |
| 75 | // invent a new one. |
| 76 | func loadOracleExclusions(t *testing.T) map[string]bool { |
| 77 | t.Helper() |
| 78 | data, err := os.ReadFile(filepath.FromSlash(oracleExclusionsPath)) |
| 79 | if err != nil { |
| 80 | t.Fatalf("loadOracleExclusions: %v", err) |
| 81 | } |
| 82 | out := map[string]bool{} |
| 83 | for _, line := range strings.Split(string(data), "\n") { |
| 84 | line = strings.TrimSpace(line) |
| 85 | if line == "" || strings.HasPrefix(line, "#") { |
| 86 | continue |
| 87 | } |
| 88 | out[line] = true |
| 89 | } |
| 90 | return out |
| 91 | } |
| 92 | |