package abool import ( "encoding/json" "sync" "sync/atomic" "testing" ) func TestDefaultValue(t *testing.T) { t.Parallel() v := New() if v.IsSet() { t.Fatal("Empty value of AtomicBool should be false") } v = NewBool(true) if !v.IsSet() { t.Fatal("NewValue(true) should be true") } v = NewBool(false) if v.IsSet() { t.Fatal("NewValue(false) should be false") } } func TestIsNotSet(t *testing.T) { t.Parallel() v := New() if v.IsSet() == v.IsNotSet() { t.Fatal("AtomicBool.IsNotSet() should be the opposite of IsSet()") } } func TestSetUnSet(t *testing.T) { t.Parallel() v := New() v.Set() if !v.IsSet() { t.Fatal("AtomicBool.Set() failed") } v.UnSet() if v.IsSet() { t.Fatal("AtomicBool.UnSet() failed") } } func TestSetTo(t *testing.T) { t.Parallel() v := New() v.SetTo(true) if !v.IsSet() { t.Fatal("AtomicBool.SetTo(true) failed") } v.SetTo(false) if v.IsSet() { t.Fatal("AtomicBool.SetTo(false) failed") } if set := v.SetToIf(true, false); set || v.IsSet() { t.Fatal("AtomicBool.SetTo(true, false) failed") } if set := v.SetToIf(false, true); !set || !v.IsSet() { t.Fatal("AtomicBool.SetTo(false, true) failed") } } func TestToggle(t *testing.T) { t.Parallel() v := New() _ = v.Toggle() if !v.IsSet() { t.Fatal("AtomicBool.Toggle() to true failed") } prev := v.Toggle() if v.IsSet() == prev { t.Fatal("AtomicBool.Toggle() to false failed") } } func TestToogleMultipleTimes(t *testing.T) { t.Parallel() v := New() pre := !v.IsSet() for i := 0; i < 100; i++ { v.SetTo(false) for j := 0; j < i; j++ { pre = v.Toggle() } expected := i%2 != 0 if v.IsSet() != expected { t.Fatalf("AtomicBool.Toogle() doesn't work after %d calls, expected: %v, got %v", i, expected, v.IsSet()) } if pre == v.IsSet() { t.Fatalf("AtomicBool.Toogle() returned wrong value at the %dth calls, expected: %v, got %v", i, !v.IsSet(), pre) } } } func TestRace(t *testing.T) { repeat := 10000 var wg sync.WaitGroup wg.Add(repeat * 3) v := New() // Writer go func() { for i := 0; i < repeat; i++ { v.Set() wg.Done() } }() // Reader go func() { for i := 0; i < repeat; i++ { v.IsSet() wg.Done() } }() // Writer go func() { for i := 0; i < repeat; i++ { v.UnSet() wg.Done() } }() wg.Wait() } func TestSetToIfAfterMultipleToggles(t *testing.T) { v := New() // false v.Toggle() // true v.Toggle() // false v.Toggle() // true // As v is true, it should now be flipped to false v.SetToIf(true, false) expected := false if v.IsSet() != expected { t.Fatalf("Toggling the value atleast 3 times, until it's true, `SetToIf(true, false)` should flip v to false, expected: %v, got %v", expected, v.IsSet()) } } func TestJSONCompatibleWithBuiltinBool(t *testing.T) { for _, value := range []bool{true, false} { // Test bool -> bytes -> AtomicBool // act 1. bool -> bytes buf, err := json.Marshal(value) if err != nil { t.Fatalf("json.Marshal(%t) failed: %s", value, err) } // act 2. bytes -> AtomicBool // // Try to unmarshall the JSON byte slice // of a normal boolean into an AtomicBool // // Create an AtomicBool with the oppsite default to ensure the unmarshal process did the work ab := NewBool(!value) err = json.Unmarshal(buf, ab) if err != nil { t.Fatalf(`json.Unmarshal("%s", %T) failed: %s`, buf, ab, err) } // assert if ab.IsSet() != value { t.Fatalf("Expected AtomicBool to represent %t but actual value was %t", value, ab.IsSet()) } // Test AtomicBool -> bytes -> bool // act 3. AtomicBool -> bytes buf, err = json.Marshal(ab) if err != nil { t.Fatalf("json.Marshal(%T) failed: %s", ab, err) } // using the opposite value for the same reason as the former case b := ab.IsNotSet() // act 4. bytes -> bool err = json.Unmarshal(buf, &b) if err != nil { t.Fatalf(`json.Unmarshal("%s", %T) failed: %s`, buf, &b, err) } // assert if b != ab.IsSet() { t.Fatalf(`json.Unmarshal("%s", %T) didn't work, expected %t, got %t`, buf, ab, ab.IsSet(), b) } } } func TestUnmarshalJSONErrorNoWrite(t *testing.T) { for _, val := range []bool{true, false} { ab := NewBool(val) oldVal := ab.IsSet() buf := []byte("invalid-json") err := json.Unmarshal(buf, ab) if err == nil { t.Fatalf(`Error expected from json.Unmarshal("%s", %T)`, buf, ab) } if oldVal != ab.IsSet() { t.Fatal("Failed json.Unmarshal modified the value of AtomicBool which is not expected") } } } func ExampleAtomicBool() { cond := New() // default to false any := true old := any new := !any cond.Set() // Sets to true cond.IsSet() // Returns true cond.UnSet() // Sets to false cond.IsNotSet() // Returns true cond.SetTo(any) // Sets to whatever you want cond.SetToIf(new, old) // Sets to `new` only if the Boolean matches the `old`, returns whether succeeded } func BenchmarkAtomicBoolToggle(b *testing.B) { v := New() b.ResetTimer() for i := 0; i < b.N; i++ { _ = v.Toggle() } } // Benchmark Read func BenchmarkMutexRead(b *testing.B) { var m sync.RWMutex var v bool b.ResetTimer() for i := 0; i < b.N; i++ { m.RLock() _ = v m.RUnlock() } } func BenchmarkAtomicValueRead(b *testing.B) { var v atomic.Value b.ResetTimer() for i := 0; i < b.N; i++ { _ = v.Load() != nil } } func BenchmarkAtomicBoolRead(b *testing.B) { v := New() b.ResetTimer() for i := 0; i < b.N; i++ { _ = v.IsSet() } } // Benchmark Write func BenchmarkMutexWrite(b *testing.B) { var m sync.RWMutex var v bool b.ResetTimer() for i := 0; i < b.N; i++ { m.RLock() v = true m.RUnlock() } b.StopTimer() _ = v } func BenchmarkAtomicValueWrite(b *testing.B) { var v atomic.Value b.ResetTimer() for i := 0; i < b.N; i++ { v.Store(true) } } func BenchmarkAtomicBoolWrite(b *testing.B) { v := New() b.ResetTimer() for i := 0; i < b.N; i++ { v.Set() } } // Benchmark CAS func BenchmarkMutexCAS(b *testing.B) { var m sync.RWMutex var v bool b.ResetTimer() for i := 0; i < b.N; i++ { m.Lock() if !v { v = true } m.Unlock() } } func BenchmarkAtomicBoolCAS(b *testing.B) { v := New() b.ResetTimer() for i := 0; i < b.N; i++ { v.SetToIf(false, true) } }