//////////////////////////////////////////////////////////////////////////////// // Copyright © 2022 xx foundation // // // // Use of this source code is governed by a license that can be found in the // // LICENSE file. // //////////////////////////////////////////////////////////////////////////////// package stoppable import ( "fmt" "sync/atomic" "testing" "time" ) // Tests that NewSingle returns a Single with the correct name and running. func TestNewSingle(t *testing.T) { name := "threadName" single := NewSingle(name) if single.name != name { t.Errorf("NewSingle returned Single with incorrect name."+ "\nexpected: %s\nreceived: %s", name, single.name) } if single.status != Running { t.Errorf("NewSingle returned Single with incorrect status."+ "\nexpected: %s\nreceived: %s", Running, single.status) } } // Unit test of Single.Name. func TestSingle_Name(t *testing.T) { name := "threadName" single := NewSingle(name) if name != single.Name() { t.Errorf("Name did not return the expected name."+ "\nexpected: %s\nreceived: %s", name, single.Name()) } } // Tests that Single.GetStatus returns the expected Status. func TestSingle_GetStatus(t *testing.T) { single := NewSingle("threadName") status := single.GetStatus() if status != Running { t.Errorf("GetStatus returned the wrong status."+ "\nexpected: %s\nreceived: %s", Running, status) } atomic.StoreUint32((*uint32)(&single.status), uint32(Stopping)) status = single.GetStatus() if status != Stopping { t.Errorf("GetStatus returned the wrong status."+ "\nexpected: %s\nreceived: %s", Stopping, status) } atomic.StoreUint32((*uint32)(&single.status), uint32(Stopped)) status = single.GetStatus() if status != Stopped { t.Errorf("GetStatus returned the wrong status."+ "\nexpected: %s\nreceived: %s", Stopped, status) } } // Tests that Single.IsRunning returns the expected value when the Single is // marked as running, stopping, and stopped. func TestSingle_IsRunning(t *testing.T) { single := NewSingle("threadName") if result := single.IsRunning(); !result { t.Errorf("IsRunning returned the wrong value when running."+ "\nexpected: %t\nreceived: %t", true, result) } single.status = Stopping if result := single.IsRunning(); result { t.Errorf("IsRunning returned the wrong value when stopping."+ "\nexpected: %t\nreceived: %t", false, result) } single.status = Stopped if result := single.IsRunning(); result { t.Errorf("IsRunning returned the wrong value when stopped."+ "\nexpected: %t\nreceived: %t", false, result) } } // Tests that Single.IsStopping returns the expected value when the Single is // marked as running, stopping, and stopped. func TestSingle_IsStopping(t *testing.T) { single := NewSingle("threadName") if result := single.IsStopping(); result { t.Errorf("IsStopping returned the wrong value when running."+ "\nexpected: %t\nreceived: %t", true, result) } single.status = Stopping if result := single.IsStopping(); !result { t.Errorf("IsStopping returned the wrong value when stopping."+ "\nexpected: %t\nreceived: %t", false, result) } single.status = Stopped if result := single.IsStopping(); result { t.Errorf("IsStopping returned the wrong value when stopped."+ "\nexpected: %t\nreceived: %t", false, result) } } // Tests that Single.IsStopped returns the expected value when the Single is // marked as running, stopping, and stopped. func TestSingle_IsStopped(t *testing.T) { single := NewSingle("threadName") if result := single.IsStopped(); result { t.Errorf("IsStopped returned the wrong value when running."+ "\nexpected: %t\nreceived: %t", true, result) } single.status = Stopping if result := single.IsStopped(); result { t.Errorf("IsStopped returned the wrong value when stopping."+ "\nexpected: %t\nreceived: %t", false, result) } single.status = Stopped if result := single.IsStopped(); !result { t.Errorf("IsStopped returned the wrong value when stopped."+ "\nexpected: %t\nreceived: %t", false, result) } } // Tests that Single.toStopping changes the status to stopping. func TestSingle_toStopping(t *testing.T) { single := NewSingle("threadName") err := single.toStopping() if err != nil { t.Errorf("toStopping returned an error: %+v", err) } if single.status != Stopping { t.Errorf("toStopping failed to set the status correctly."+ "\nexpected: %s\nreceived: %s", Stopping, single.status) } } // Error path: tests that Single.toStopping returns an error when failing to // change the status to stopping when the current status is not running. func TestSingle_toStopping_StatusError(t *testing.T) { single := NewSingle("threadName") single.status = Stopped expectedErr := fmt.Sprintf( toStoppingErr, single.Name(), single.GetStatus(), Running) err := single.toStopping() if err == nil || err.Error() != expectedErr { t.Errorf("toStopping failed to return the expected error."+ "\nexpected: %s\nreceived: %+v", expectedErr, err) } if single.status != Stopped { t.Errorf("toStopping changed the status when the compare failed."+ "\nexpected: %s\nreceived: %s", Stopped, single.status) } } // Tests that Single.ToStopped changes the status to stopped. func TestSingle_ToStopped(t *testing.T) { single := NewSingle("threadName") single.status = Stopping single.ToStopped() if single.status != Stopped { t.Errorf("ToStopped failed to set the status correctly."+ "\nexpected: %s\nreceived: %s", Stopped, single.status) } } // Panic path: tests that Single.ToStopped panics when failing to change the // status to stopped when the current status is not stopping. func TestSingle_ToStopped_StatusPanic(t *testing.T) { single := NewSingle("threadName") defer func() { if r := recover(); r == nil { t.Errorf("ToStopped failed to panic when the status should not " + "have changed.") } else { if single.status != Running { t.Errorf("ToStopped changed the status when the compare failed."+ "\nexpected: %s\nreceived: %s", Running, single.status) } } }() single.status = Running single.ToStopped() } // Tests that Single.Quit returns a channel that is triggered when the Single // quit channel is triggered. func TestSingle_Quit(t *testing.T) { single := NewSingle("threadName") go func() { select { case <-time.NewTimer(5 * time.Millisecond).C: t.Error("Timed out waiting for quit channel.") case <-single.Quit(): } }() single.quit <- struct{}{} } // Test happy path of Single.Close(). func TestSingle_Close(t *testing.T) { single := NewSingle("threadName") timeout := 10 * time.Millisecond go func() { select { case <-time.NewTimer(timeout).C: t.Errorf("Timed out waiting to receive on quit channel after %s.", timeout) case <-single.Quit(): if !single.IsStopping() { t.Errorf("Status of stoppable incorrect."+ "\nexpected: %s\nreceived: %s", Stopping, single.status) } atomic.StoreUint32((*uint32)(&single.status), uint32(Stopped)) } }() err := single.Close() if err != nil { t.Errorf("Close returned an error: %v", err) } } // Error path: tests that Single.Close returns an error when the status fails // to change to stopping. func TestSingle_Close_Error(t *testing.T) { single := NewSingle("threadName") single.status = Stopped expectedErr := fmt.Sprintf( toStoppingErr, single.Name(), single.GetStatus(), Running) err := single.Close() if err == nil || err.Error() != expectedErr { t.Errorf("Close did not return the expected error."+ "\nexpected: %s\nreceived: %v", expectedErr, err) } }