TestFindPathsToKey_CycleDetected corrupts the db so that a branch page points at its own ancestor, then verifies that FindPathsToKey returns an error instead of recursing until stack overflow. See issue #701 for the real-world corruption pattern (power-off creating a page cycle).
(t *testing.T)
| 40 | // error instead of recursing until stack overflow. See issue #701 for the |
| 41 | // real-world corruption pattern (power-off creating a page cycle). |
| 42 | func TestFindPathsToKey_CycleDetected(t *testing.T) { |
| 43 | db := btesting.MustCreateDB(t) |
| 44 | require.NoError(t, |
| 45 | db.Fill([]byte("data"), 1, 500, |
| 46 | func(tx int, k int) []byte { return []byte(fmt.Sprintf("%04d", k)) }, |
| 47 | func(tx int, k int) []byte { return make([]byte, 100) }, |
| 48 | )) |
| 49 | require.NoError(t, db.Close()) |
| 50 | |
| 51 | // Find the path to an arbitrary key so we can pick a branch ancestor |
| 52 | // and one of its leaf descendants to corrupt. |
| 53 | navigator := surgeon.NewXRay(db.Path()) |
| 54 | paths, err := navigator.FindPathsToKey([]byte("0001")) |
| 55 | require.NoError(t, err) |
| 56 | require.NotEmpty(t, paths) |
| 57 | path := paths[0] |
| 58 | require.GreaterOrEqual(t, len(path), 2, "need at least one branch above the leaf") |
| 59 | |
| 60 | // Overwrite the leaf page with a copy of its branch ancestor. The |
| 61 | // rewritten page is still a branch and now references its own pgid |
| 62 | // through the ancestor's element list, forming a cycle. |
| 63 | ancestor := path[len(path)-2] |
| 64 | leaf := path[len(path)-1] |
| 65 | require.NoError(t, surgeon.CopyPage(db.Path(), ancestor, leaf)) |
| 66 | |
| 67 | // Confirm the ancestor actually lists the leaf as one of its children, |
| 68 | // so copying creates a real cycle rather than two disjoint branches. |
| 69 | ancestorPage, _, err := guts_cli.ReadPage(db.Path(), uint64(ancestor)) |
| 70 | require.NoError(t, err) |
| 71 | require.True(t, ancestorPage.IsBranchPage()) |
| 72 | var hasLeafAsChild bool |
| 73 | for i := uint16(0); i < ancestorPage.Count(); i++ { |
| 74 | if ancestorPage.BranchPageElement(i).Pgid() == common.Pgid(leaf) { |
| 75 | hasLeafAsChild = true |
| 76 | break |
| 77 | } |
| 78 | } |
| 79 | require.True(t, hasLeafAsChild, "expected ancestor to reference the leaf directly") |
| 80 | |
| 81 | _, err = surgeon.NewXRay(db.Path()).FindPathsToKey([]byte("0001")) |
| 82 | require.Error(t, err) |
| 83 | require.Contains(t, err.Error(), "cycle detected") |
| 84 | } |
| 85 | |
| 86 | // TestFindPathsToKey_MultipleBuckets ensures the shared visited map in |
| 87 | // traverse does not suppress legitimate paths when the same key appears |
nothing calls this directly
no test coverage detected