package main import ( "testing" "golang.org/x/sys/unix" ) const sampleMountinfo = `22 1 252:0 / / rw,relatime shared:1 - ext4 /dev/mapper/vg-root rw 30 22 0:5 / /dev rw,nosuid shared:2 - devtmpfs udev rw 95 22 8:1 / /media/u/Data rw,relatime shared:50 - ext4 /dev/sda1 rw 96 22 8:1 /photos /mnt/photos\040bind rw,relatime shared:50 - ext4 /dev/sda1 rw ` func TestParseMountinfo(t *testing.T) { es := parseMountinfo(sampleMountinfo) if len(es) != 4 { t.Fatalf("应解析出 4 条:%d", len(es)) } if e := es[3]; e.Point != "/mnt/photos bind" || e.Root != "/photos" || e.Major != 8 || e.Minor != 1 || e.FSType != "ext4" || e.Source != "/dev/sda1" { t.Errorf("解析不正确:%+v", e) } } // fakeSystem 模拟挂载表、UUID 与设备号 type fakeSystem struct { info string uuid map[uint64]string devs map[string]uint64 // 路径前缀 → 设备号(最长匹配) stats []string } func (f *fakeSystem) mountinfo() []mountEntry { return parseMountinfo(f.info) } func (f *fakeSystem) uuids() map[uint64]string { return f.uuid } func (f *fakeSystem) devOf(p string) (uint64, error) { f.stats = append(f.stats, p) best, dev := "", uint64(0) for prefix, d := range f.devs { if within(p, prefix) && len(prefix) >= len(best) { best, dev = prefix, d } } if best == "" { return 0, unix.ENOENT } return dev, nil } func TestIdentifyAndLocate(t *testing.T) { sda := unix.Mkdev(8, 1) root := unix.Mkdev(252, 0) sys := &fakeSystem{ info: sampleMountinfo, uuid: map[uint64]string{sda: "aaaa-1111", root: "rootuuid"}, devs: map[string]uint64{"/": root, "/media/u/Data": sda, "/mnt/photos bind": sda}, } // 系统盘:只记路径 if id := identify(sys, "/home/u/docs"); !id.SystemDisk || id.UUID != "" { t.Errorf("系统盘上的目录:%+v", id) } // 外接盘:UUID + 设备内路径 id := identify(sys, "/media/u/Data/资料/合同") if id.UUID != "aaaa-1111" || id.RelPath != "/资料/合同" || id.SystemDisk || id.MountPoint != "/media/u/Data" { t.Fatalf("外接盘上的目录:%+v", id) } src := localSource{Path: id.Path, UUID: id.UUID, RelPath: id.RelPath} if p, err := locate(sys, src); err != nil || p != "/media/u/Data/资料/合同" { t.Errorf("设备在原位置:%q %v", p, err) } // bind mount 的子目录 if p, err := locate(sys, localSource{Path: "/mnt/photos bind/2024", UUID: "aaaa-1111", RelPath: "/photos/2024"}); err != nil || p != "/mnt/photos bind/2024" { t.Errorf("bind mount:%q %v", p, err) } // 设备拔出:UUID 不在了 sys.uuid = map[uint64]string{root: "rootuuid"} if _, err := locate(sys, src); err == nil || err.State != "device_missing" { t.Errorf("设备拔出应为 device_missing:%v", err) } // 设备插回,但挂到了别的位置(sdb) sdb := unix.Mkdev(8, 17) sys.uuid = map[uint64]string{sdb: "aaaa-1111", root: "rootuuid"} sys.info = "22 1 252:0 / / rw - ext4 /dev/mapper/vg-root rw\n97 22 8:17 / /media/u/Data1 rw - ext4 /dev/sdb1 rw\n" if p, err := locate(sys, src); err != nil || p != "/media/u/Data1/资料/合同" { t.Errorf("挂载位置变化后应能找到:%q %v", p, err) } // 另一块盘挂在原路径上:UUID 不同,不会被误认 sys.uuid = map[uint64]string{sdb: "bbbb-2222", root: "rootuuid"} sys.info = "22 1 252:0 / / rw - ext4 /dev/mapper/vg-root rw\n97 22 8:17 / /media/u/Data rw - ext4 /dev/sdb1 rw\n" if _, err := locate(sys, src); err == nil || err.State != "device_missing" { t.Errorf("换了一块盘应为 device_missing:%v", err) } // 设备已连接但没有挂载(例如自动挂载):会访问原路径触发挂载 sys.uuid = map[uint64]string{sda: "aaaa-1111", root: "rootuuid"} sys.info = "22 1 252:0 / / rw - ext4 /dev/mapper/vg-root rw\n" sys.stats = nil if _, err := locate(sys, src); err == nil || err.State != "device_missing" || !contains(err.Message, "没有挂载") { t.Errorf("已连接未挂载:%v", err) } if len(sys.stats) == 0 || sys.stats[0] != src.Path { t.Errorf("应访问原路径以触发自动挂载:%v", sys.stats) } // 只记路径的目录 if p, err := locate(sys, localSource{Path: "/home/u/docs"}); err != nil || p != "/home/u/docs" { t.Errorf("系统盘:%q %v", p, err) } }