You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

163 lines
4.5 KiB

package blockvol
import (
"errors"
"os"
"path/filepath"
"testing"
)
func TestParseDurabilityMode_Valid(t *testing.T) {
tests := []struct {
input string
want DurabilityMode
}{
{"best_effort", DurabilityBestEffort},
{"sync_all", DurabilitySyncAll},
{"sync_quorum", DurabilitySyncQuorum},
{"", DurabilityBestEffort}, // empty = backward compat
}
for _, tt := range tests {
got, err := ParseDurabilityMode(tt.input)
if err != nil {
t.Errorf("ParseDurabilityMode(%q) error: %v", tt.input, err)
}
if got != tt.want {
t.Errorf("ParseDurabilityMode(%q) = %v, want %v", tt.input, got, tt.want)
}
}
}
func TestParseDurabilityMode_Invalid(t *testing.T) {
_, err := ParseDurabilityMode("invalid_mode")
if !errors.Is(err, ErrInvalidDurabilityMode) {
t.Errorf("ParseDurabilityMode(invalid) error = %v, want ErrInvalidDurabilityMode", err)
}
}
func TestDurabilityMode_StringRoundTrip(t *testing.T) {
for _, m := range []DurabilityMode{DurabilityBestEffort, DurabilitySyncAll, DurabilitySyncQuorum} {
s := m.String()
got, err := ParseDurabilityMode(s)
if err != nil {
t.Errorf("round-trip %v -> %q: parse error: %v", m, s, err)
}
if got != m {
t.Errorf("round-trip %v -> %q -> %v", m, s, got)
}
}
}
func TestDurabilityMode_Validate_SyncQuorum_RF2_Rejected(t *testing.T) {
err := DurabilitySyncQuorum.Validate(2)
if !errors.Is(err, ErrSyncQuorumRequiresRF3) {
t.Errorf("Validate(sync_quorum, RF=2) = %v, want ErrSyncQuorumRequiresRF3", err)
}
}
func TestDurabilityMode_Validate_SyncQuorum_RF3_OK(t *testing.T) {
if err := DurabilitySyncQuorum.Validate(3); err != nil {
t.Errorf("Validate(sync_quorum, RF=3) = %v, want nil", err)
}
}
func TestDurabilityMode_Validate_BestEffort_RF1_OK(t *testing.T) {
if err := DurabilityBestEffort.Validate(1); err != nil {
t.Errorf("Validate(best_effort, RF=1) = %v, want nil", err)
}
}
func TestDurabilityMode_RequiredReplicas_SyncAll_RF2(t *testing.T) {
got := DurabilitySyncAll.RequiredReplicas(2)
if got != 1 {
t.Errorf("RequiredReplicas(sync_all, RF=2) = %d, want 1", got)
}
}
func TestDurabilityMode_RequiredReplicas_SyncQuorum_RF3(t *testing.T) {
got := DurabilitySyncQuorum.RequiredReplicas(3)
if got != 1 { // quorum=2, primary counts as 1, need 1 replica
t.Errorf("RequiredReplicas(sync_quorum, RF=3) = %d, want 1", got)
}
}
func TestDurabilityMode_IsStrict(t *testing.T) {
if DurabilityBestEffort.IsStrict() {
t.Error("best_effort should not be strict")
}
if !DurabilitySyncAll.IsStrict() {
t.Error("sync_all should be strict")
}
if !DurabilitySyncQuorum.IsStrict() {
t.Error("sync_quorum should be strict")
}
}
func TestDurabilityMode_CreateCloseOpenRoundTrip(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.blk")
vol, err := CreateBlockVol(path, CreateOptions{
VolumeSize: 4 * 1024 * 1024,
DurabilityMode: DurabilitySyncAll,
})
if err != nil {
t.Fatalf("Create: %v", err)
}
if vol.DurabilityMode() != DurabilitySyncAll {
t.Fatalf("DurabilityMode() = %v, want sync_all", vol.DurabilityMode())
}
vol.Close()
vol2, err := OpenBlockVol(path)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer vol2.Close()
if vol2.DurabilityMode() != DurabilitySyncAll {
t.Errorf("DurabilityMode() after reopen = %v, want sync_all", vol2.DurabilityMode())
}
}
func TestDurabilityMode_Accessor_Default(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.blk")
vol, err := CreateBlockVol(path, CreateOptions{VolumeSize: 4 * 1024 * 1024})
if err != nil {
t.Fatalf("Create: %v", err)
}
defer vol.Close()
if vol.DurabilityMode() != DurabilityBestEffort {
t.Errorf("default DurabilityMode() = %v, want best_effort", vol.DurabilityMode())
}
}
func TestDurabilityMode_InvalidOnDisk_OpenFails(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.blk")
vol, err := CreateBlockVol(path, CreateOptions{VolumeSize: 4 * 1024 * 1024})
if err != nil {
t.Fatalf("Create: %v", err)
}
vol.Close()
// Corrupt the DurabilityMode byte on disk.
// Epoch ends at offset 104. DurabilityMode is at offset 104.
// Actually, let me compute: 4(magic)+2(ver)+2(flags)+16(uuid)+8(volsz)+4(ext)+4(blk)+
// 8(waloff)+8(walsz)+8(walhead)+8(waltail)+8(walcplsn)+4(repl)+8(created)+4(snapcnt)+8(epoch) = 104
f, err := os.OpenFile(path, os.O_RDWR, 0644)
if err != nil {
t.Fatalf("open: %v", err)
}
if _, err := f.WriteAt([]byte{99}, 104); err != nil {
t.Fatalf("write corrupt byte: %v", err)
}
f.Close()
_, err = OpenBlockVol(path)
if err == nil {
t.Fatal("OpenBlockVol should fail with invalid DurabilityMode")
}
}