| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- package core
- import (
- "encoding/binary"
- "errors"
- "os"
- "testing"
- "git.ycl.pub/yl32940/TsDB2/DB/core/page"
- "git.ycl.pub/yl32940/TsDB2/DB/core/types"
- )
- // 直接读写盘上头页某偏移的辅助 (绕过引擎, 模拟损坏/旧库)。
- func corruptHeaderByte(t *testing.T, path string, off int64, xor byte) {
- t.Helper()
- f, err := os.OpenFile(path, os.O_RDWR, 0)
- if err != nil {
- t.Fatal(err)
- }
- defer f.Close()
- var b [1]byte
- if _, err := f.ReadAt(b[:], off); err != nil {
- t.Fatal(err)
- }
- b[0] ^= xor
- if _, err := f.WriteAt(b[:], off); err != nil {
- t.Fatal(err)
- }
- }
- // zeroHeaderCrc 把盘上 HeaderCrc 字段清零, 模拟"没有 CRC 字段的旧 v11 库"。
- func zeroHeaderCrc(t *testing.T, path string) {
- t.Helper()
- f, err := os.OpenFile(path, os.O_RDWR, 0)
- if err != nil {
- t.Fatal(err)
- }
- defer f.Close()
- zero := make([]byte, 4)
- if _, err := f.WriteAt(zero, 84); err != nil {
- t.Fatal(err)
- }
- }
- // seedCrcDB 建一个带数据的库并正常关闭, 返回路径。
- func seedCrcDB(t *testing.T) string {
- t.Helper()
- path := t.TempDir() + "/crc.db"
- db, err := Open(path)
- if err != nil {
- t.Fatal(err)
- }
- db.SetAutoCommitDelay(0)
- tbl, err := db.CreateTable("t", []types.FieldInfo{
- {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
- {FieldType: types.FieldType_Int64, FieldName: []byte("v")},
- })
- if err != nil {
- t.Fatal(err)
- }
- if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
- t.Fatal(err)
- }
- for i := 1; i <= 50; i++ {
- if _, err := tbl.InsertRowWithID(uint64(i), []types.FieldValue{
- {FieldName: []byte("id"), Value: uint64(i)},
- {FieldName: []byte("v"), Value: int64(i)},
- }); err != nil {
- t.Fatal(err)
- }
- }
- if err := db.Commit(); err != nil {
- t.Fatal(err)
- }
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- return path
- }
- // TestHeaderCrcRoundtrip 新建库: 首次落盘的头页 CRC 自洽, 打开通过。
- func TestHeaderCrcRoundtrip(t *testing.T) {
- path := seedCrcDB(t)
- db, err := Open(path)
- if err != nil {
- t.Fatalf("正常库打开失败: %v", err)
- }
- defer db.Close()
- // 内存头页 CRC 与重算值一致
- if db.header.HeaderCrc == 0 {
- t.Fatal("新建库的 HeaderCrc 不应为 0")
- }
- if got := db.header.CalcHeaderCrc(); got != db.header.HeaderCrc {
- t.Fatalf("内存头页 CRC %08x 与重算 %08x 不一致", db.header.HeaderCrc, got)
- }
- if err := db.header.Validate(); err != nil {
- t.Fatalf("Validate: %v", err)
- }
- }
- // TestHeaderCrcCorrupted 损坏头页数据 → 正常打开拒绝 → force-inspect 只读抢救:
- // 读可用、写报 ErrReadOnly。
- func TestHeaderCrcCorrupted(t *testing.T) {
- path := seedCrcDB(t)
- // 损坏头页 Comment 区的一个字节 (CRC 必不匹配)
- corruptHeaderByte(t, path, 500, 0xFF)
- if _, err := Open(path); err == nil {
- t.Fatal("CRC 损坏的库正常模式必须拒绝打开")
- }
- db, err := OpenWithOptions(path, OpenOptions{ForceInspect: true})
- if err != nil {
- t.Fatalf("force-inspect 应能打开: %v", err)
- }
- defer db.Close()
- // 读正常
- tbl, ok := db.FindTable("t")
- if !ok {
- t.Fatal("抢救模式找不到表")
- }
- if c := vacCountOf(t, tbl); c != 50 {
- t.Fatalf("抢救模式读到 %d 行, 想要 50", c)
- }
- // 写被禁
- if _, err := tbl.InsertRowWithID(999, []types.FieldValue{
- {FieldName: []byte("id"), Value: uint64(999)},
- {FieldName: []byte("v"), Value: int64(1)},
- }); err == nil {
- t.Fatal("抢救模式插入应失败")
- }
- if err := db.Commit(); !errors.Is(err, ErrReadOnly) {
- t.Fatalf("抢救模式 Commit 应返回 ErrReadOnly, 得到 %v", err)
- }
- if err := db.SetComment("x"); !errors.Is(err, ErrReadOnly) {
- t.Fatalf("抢救模式 SetComment 应返回 ErrReadOnly, 得到 %v", err)
- }
- }
- // TestHeaderCrcLegacyV11 旧 v11 库 (HeaderCrc==0) 加载跳过校验; 第一次写头页
- // 自动生成 CRC, 之后打开走校验。
- func TestHeaderCrcLegacyV11(t *testing.T) {
- path := seedCrcDB(t)
- zeroHeaderCrc(t, path)
- // 旧库正常打开 (跳过 CRC)
- db, err := Open(path)
- if err != nil {
- t.Fatalf("旧 v11 库应跳过 CRC 打开: %v", err)
- }
- // 任意触发头页写 (SetComment 直写头页)
- if err := db.SetComment("升级后的介绍"); err != nil {
- t.Fatalf("SetComment: %v", err)
- }
- _ = db.Close()
- // 现在盘上已有 CRC: 正常打开且 CRC 参与校验
- db2, err := Open(path)
- if err != nil {
- t.Fatalf("自动生成 CRC 后应正常打开: %v", err)
- }
- defer db2.Close()
- if db2.header.HeaderCrc == 0 {
- t.Fatal("首次写头页后 CRC 应已生成")
- }
- if err := db2.header.Validate(); err != nil {
- t.Fatalf("升级后 Validate: %v", err)
- }
- if db2.Comment() != "升级后的介绍" {
- t.Fatalf("Comment = %q", db2.Comment())
- }
- }
- // TestHeaderCrcCoversFullPage CRC 覆盖整页: 改数据页内容不影响头页校验,
- // 改头页任意字节 (含尾部) 都会失配。
- func TestHeaderCrcCoversFullPage(t *testing.T) {
- path := seedCrcDB(t)
- // 读出头页 CRC
- raw, err := os.ReadFile(path)
- if err != nil {
- t.Fatal(err)
- }
- stored := binary.LittleEndian.Uint32(raw[84:88])
- if stored == 0 {
- t.Fatal("新建库头页 CRC 不应为 0")
- }
- // 改头页尾部保留区的一个字节 → 失配
- corruptHeaderByte(t, path, 4000, 0x01)
- db, err := Open(path)
- if err == nil {
- db.Close()
- t.Fatal("头页尾部字节被改后应校验失败")
- }
- }
|