| 1 | package calc |
| 2 | |
| 3 | import ( |
| 4 | "testing" |
| 5 | "time" |
| 6 | ) |
| 7 | |
| 8 | // The graph tests key cells by hand rather than through a real binder |
| 9 | // (task 10 owns binding). RowID/ColID here are exactly refKey's position- |
| 10 | // plus-one convention, so a formula's "A1" and the test's cellA1 name the |
| 11 | // same CellKey. |
| 12 | var ( |
| 13 | cellA1 = CellKey{RowID: 1, ColID: 1} |
| 14 | cellB1 = CellKey{RowID: 1, ColID: 2} |
| 15 | cellC1 = CellKey{RowID: 1, ColID: 3} |
| 16 | cellZ9 = CellKey{RowID: 9, ColID: 26} |
| 17 | ) |
| 18 | |
| 19 | // env gives A1 a raw input value so B1 (=A1*2) and C1 (=B1+1) have |
| 20 | // something concrete to compute from; it is shared read-only by every |
| 21 | // test in this file, none of which mutate it. |
| 22 | var env = mapEnv{cellA1: {Kind: KindNum, Num: 10}} |
| 23 | |
| 24 | func TestDirtySetTouchesOnlyDependents(t *testing.T) { |
| 25 | g := NewGraph() |
| 26 | g.SetFormula(cellB1, mustParse(t, "A1*2")) |
| 27 | g.SetFormula(cellC1, mustParse(t, "B1+1")) |
| 28 | g.SetFormula(cellZ9, mustParse(t, "42")) // unrelated |
| 29 | |
| 30 | touched := g.Recalc(cellA1, env) |
| 31 | if touched != 2 { |
| 32 | t.Fatalf("touched %d cells, want 2 (B1, C1) — Z9 must not recalc", touched) |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | // A cycle must return an error value, not hang. This test has a timeout |
| 37 | // because the failure mode of getting this wrong is a wedged process. |
| 38 | func TestCycleDetection(t *testing.T) { |
| 39 | g := NewGraph() |
| 40 | g.SetFormula(cellA1, mustParse(t, "B1")) |
| 41 | g.SetFormula(cellB1, mustParse(t, "A1")) |
| 42 | done := make(chan Value, 1) |
| 43 | go func() { done <- g.Value(cellA1, env) }() |
| 44 | select { |
| 45 | case v := <-done: |
| 46 | if v.Kind != KindErr || v.Err != ErrCircular { |
| 47 | t.Fatalf("cycle = %v, want #CIRCULAR!", v) |
| 48 | } |
| 49 | case <-time.After(2 * time.Second): |
| 50 | t.Fatal("cycle detection hung") |
| 51 | } |
| 52 | } |
| 53 | |