From 0d0dbd3608cfd2d71956b26e7ebdf699f2e021b1 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Thu, 7 Nov 2024 17:23:08 +0900 Subject: [PATCH 1/3] added Shutdown method in tesitng --- issues_test.go | 33 ++++++++++++++++-- shared_directory_test.go | 34 ++++++------------- socket_test.go | 7 +++- storage_test.go | 9 +++-- virtualization_test.go | 72 ++++++++++++++++++++++++++++++++++------ 5 files changed, 116 insertions(+), 39 deletions(-) diff --git a/issues_test.go b/issues_test.go index 076cec73..c029a89d 100644 --- a/issues_test.go +++ b/issues_test.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strings" "testing" + "time" "github.com/Code-Hex/vz/v3/internal/objc" ) @@ -299,15 +300,41 @@ func TestIssue119(t *testing.T) { objc.Retain(vm.pointer) vm.finalize() - // sshSession.Run("poweroff") - vm.Pause() + sendStop := false + if vm.CanStop() { + if err := vm.Stop(); err != nil { + t.Error(err) + } + sendStop = true + } + if vm.CanRequestStop() { + if _, err := vm.RequestStop(); err != nil { + t.Error(err) + } + sendStop = true + } + if !sendStop { + t.Fatal("unexpected failed to send stop signal") + } + + timer := time.After(3 * time.Second) + for { + select { + case state := <-vm.StateChangedNotify(): + if VirtualMachineStateStopped == state { + return + } + case <-timer: + t.Fatal("failed to shutdown vm") + } + } } func setupIssue119Config(bootLoader *LinuxBootLoader) (*VirtualMachineConfiguration, error) { config, err := NewVirtualMachineConfiguration( bootLoader, 1, - 512*1024*1024, + 256*1024*1024, ) if err != nil { return nil, fmt.Errorf("failed to create a new virtual machine config: %w", err) diff --git a/shared_directory_test.go b/shared_directory_test.go index 19e3d728..ed02464a 100644 --- a/shared_directory_test.go +++ b/shared_directory_test.go @@ -3,11 +3,11 @@ package vz_test import ( "bytes" "fmt" + "log" "os" "path/filepath" "strings" "testing" - "time" "github.com/Code-Hex/vz/v3" ) @@ -78,9 +78,11 @@ func TestSingleDirectoryShare(t *testing.T) { return nil }, ) - defer container.Close() - - vm := container.VirtualMachine + t.Cleanup(func() { + if err := container.Shutdown(); err != nil { + log.Println(err) + } + }) file := "hello.txt" for _, v := range []struct { @@ -133,14 +135,6 @@ func TestSingleDirectoryShare(t *testing.T) { t.Fatalf("failed to run command %q: %v\nstderr: %q", check, err, buf) } session.Close() - - if err := vm.Stop(); err != nil { - t.Fatal(err) - } - - timeout := 3 * time.Second - waitState(t, timeout, vm, vz.VirtualMachineStateStopping) - waitState(t, timeout, vm, vz.VirtualMachineStateStopped) }) } } @@ -187,9 +181,11 @@ func TestMultipleDirectoryShare(t *testing.T) { return nil }, ) - defer container.Close() - - vm := container.VirtualMachine + t.Cleanup(func() { + if err := container.Shutdown(); err != nil { + log.Println(err) + } + }) // Create a file in mount directories. tmpFile := "tmp.txt" @@ -244,12 +240,4 @@ func TestMultipleDirectoryShare(t *testing.T) { if err != nil { t.Fatalf("expected the file to exist in read/write directory: %v", err) } - - if err := vm.Stop(); err != nil { - t.Fatal(err) - } - - timeout := 3 * time.Second - waitState(t, timeout, vm, vz.VirtualMachineStateStopping) - waitState(t, timeout, vm, vz.VirtualMachineStateStopped) } diff --git a/socket_test.go b/socket_test.go index badff23a..72128cb4 100644 --- a/socket_test.go +++ b/socket_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "log" "testing" "time" @@ -12,7 +13,11 @@ import ( func TestVirtioSocketListener(t *testing.T) { container := newVirtualizationMachine(t) - defer container.Close() + t.Cleanup(func() { + if err := container.Shutdown(); err != nil { + log.Println(err) + } + }) vm := container.VirtualMachine diff --git a/storage_test.go b/storage_test.go index 834cc3bd..8c18e48e 100644 --- a/storage_test.go +++ b/storage_test.go @@ -1,6 +1,7 @@ package vz_test import ( + "log" "path/filepath" "strings" "testing" @@ -76,7 +77,7 @@ func TestBlockDeviceWithCacheAndSyncMode(t *testing.T) { t.Fatal(err) } - attachment, err := vz.NewDiskImageStorageDeviceAttachmentWithCacheAndSync(path, false, vz.DiskImageCachingModeAutomatic, vz.DiskImageSynchronizationModeFsync) + attachment, err := vz.NewDiskImageStorageDeviceAttachmentWithCacheAndSync(path, false, vz.DiskImageCachingModeCached, vz.DiskImageSynchronizationModeFsync) if err != nil { t.Fatal(err) } @@ -90,7 +91,11 @@ func TestBlockDeviceWithCacheAndSyncMode(t *testing.T) { return nil }, ) - defer container.Close() + t.Cleanup(func() { + if err := container.Shutdown(); err != nil { + log.Println(err) + } + }) vm := container.VirtualMachine diff --git a/virtualization_test.go b/virtualization_test.go index 2a1f3e7d..6892d147 100644 --- a/virtualization_test.go +++ b/virtualization_test.go @@ -56,7 +56,7 @@ func setupConfiguration(bootLoader vz.BootLoader) (*vz.VirtualMachineConfigurati config, err := vz.NewVirtualMachineConfiguration( bootLoader, 1, - 512*1024*1024, + 256*1024*1024, ) if err != nil { return nil, fmt.Errorf("failed to create a new virtual machine config: %w", err) @@ -101,10 +101,6 @@ type Container struct { *ssh.Client } -func (c *Container) Close() error { - return c.Client.Close() -} - func (c *Container) NewSession(t *testing.T) *ssh.Session { sshSession, err := c.Client.NewSession() if err != nil { @@ -114,6 +110,50 @@ func (c *Container) NewSession(t *testing.T) *ssh.Session { return sshSession } +func (c *Container) Shutdown() error { + defer func() { + log.Println("shutdown done") + c.Client.Close() + }() + + vm := c.VirtualMachine + + if got := vm.State(); vz.VirtualMachineStateStopped == got { + return nil + } + + switch { + case vm.CanStop(): + if err := vm.Stop(); err != nil { + return fmt.Errorf("failed to call stop: %w", err) + } + case vm.CanRequestStop(): + if _, err := vm.RequestStop(); err != nil { + return fmt.Errorf("failed to send request stop: %w", err) + } + default: + sshSession, err := c.Client.NewSession() + if err != nil { + return fmt.Errorf("failed to create a new session: %w", err) + } + if err := sshSession.Run("poweroff"); err != nil { + return fmt.Errorf("failed to run poweroff command: %w", err) + } + } + + wait := time.After(3 * time.Second) + for { + select { + case got := <-vm.StateChangedNotify(): + if vz.VirtualMachineStateStopped == got { + return nil + } + case <-wait: + return fmt.Errorf("failed to wait stopped state") + } + } +} + func getFreePort() (int, error) { l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { @@ -221,7 +261,7 @@ RETRY: t.Fatalf("failed to connect vsock: %v", err) } - t.Log("setup ssh client in container") + log.Println("setup ssh client in container") initialized := make(chan struct{}) retry := make(chan struct{}) @@ -248,7 +288,7 @@ RETRY: close(initialized) - t.Logf("container setup done") + log.Println("container setup done") return &Container{ VirtualMachine: vm, @@ -281,7 +321,11 @@ func TestRun(t *testing.T) { return setupConsoleConfig(vmc) }, ) - defer container.Close() + t.Cleanup(func() { + if err := container.Shutdown(); err != nil { + log.Println(err) + } + }) sshSession := container.NewSession(t) defer sshSession.Close() @@ -344,7 +388,11 @@ func TestStop(t *testing.T) { } container := newVirtualizationMachine(t) - defer container.Close() + t.Cleanup(func() { + if err := container.Shutdown(); err != nil { + log.Println(err) + } + }) vm := container.VirtualMachine @@ -415,7 +463,11 @@ func TestRunIssue124(t *testing.T) { return setupConsoleConfig(vmc) }, ) - defer container.Close() + t.Cleanup(func() { + if err := container.Shutdown(); err != nil { + log.Println(err) + } + }) sshSession := container.NewSession(t) defer sshSession.Close() From 0a4f3f0a9d502ad66932d3f6ab9dbff61481042c Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Thu, 7 Nov 2024 17:53:50 +0900 Subject: [PATCH 2/3] added waitUntilState in testing --- virtualization_test.go | 69 ++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/virtualization_test.go b/virtualization_test.go index 6892d147..9be7357d 100644 --- a/virtualization_test.go +++ b/virtualization_test.go @@ -141,17 +141,7 @@ func (c *Container) Shutdown() error { } } - wait := time.After(3 * time.Second) - for { - select { - case got := <-vm.StateChangedNotify(): - if vz.VirtualMachineStateStopped == got { - return nil - } - case <-wait: - return fmt.Errorf("failed to wait stopped state") - } - } + return waitUntilState(3*time.Second, vm, vz.VirtualMachineStateStopped) } func getFreePort() (int, error) { @@ -227,8 +217,10 @@ func newVirtualizationMachine( t.Fatal(err) } - waitState(t, 3*time.Second, vm, vz.VirtualMachineStateStarting) - waitState(t, 3*time.Second, vm, vz.VirtualMachineStateRunning) + // Starting -> Running + if err := waitUntilState(5*time.Second, vm, vz.VirtualMachineStateRunning); err != nil { + t.Fatal(err) + } sshConfig := testhelper.NewSshConfig("root", "passwd") @@ -315,6 +307,24 @@ func waitState(t *testing.T, wait time.Duration, vm *vz.VirtualMachine, want vz. } } +var ErrErrorState = fmt.Errorf("error state") + +func waitUntilState(wait time.Duration, vm *vz.VirtualMachine, want vz.VirtualMachineState) error { + for { + select { + case got := <-vm.StateChangedNotify(): + if want == got { + return nil + } + if got == vz.VirtualMachineStateError { + return ErrErrorState + } + case <-time.After(wait): + return fmt.Errorf("timedout waiting expected state: %s", want) + } + } +} + func TestRun(t *testing.T) { container := newVirtualizationMachine(t, func(vmc *vz.VirtualMachineConfiguration) error { @@ -362,20 +372,12 @@ func TestRun(t *testing.T) { if got := vm.CanRequestStop(); !got { t.Fatal("want CanRequestStop is true") } - // TODO(codehex): I need to support - // see: https://developer.apple.com/forums/thread/702160 - // - // if success, err := vm.RequestStop(); !success || err != nil { - // t.Error(success, err) - // return - // } - - // waitState(t, 5*time.Second, vm, vz.VirtualMachineStateStopping) - // waitState(t, 5*time.Second, vm, vz.VirtualMachineStateStopped) - - sshSession.Run("poweroff") + if success, err := vm.RequestStop(); !success || err != nil { + t.Error(success, err) + return + } - waitState(t, timeout, vm, vz.VirtualMachineStateStopped) + waitState(t, 5*time.Second, vm, vz.VirtualMachineStateStopped) if got := vm.State(); vz.VirtualMachineStateStopped != got { t.Fatalf("want state %v but got %v", vz.VirtualMachineStateStopped, got) @@ -404,8 +406,9 @@ func TestStop(t *testing.T) { } timeout := 3 * time.Second - waitState(t, timeout, vm, vz.VirtualMachineStateStopping) - waitState(t, timeout, vm, vz.VirtualMachineStateStopped) + if err := waitUntilState(timeout, vm, vz.VirtualMachineStateStopped); err != nil { + t.Fatal(err) + } } func TestVirtualMachineStateString(t *testing.T) { @@ -485,8 +488,9 @@ func TestRunIssue124(t *testing.T) { } timeout := 5 * time.Second - waitState(t, timeout, vm, vz.VirtualMachineStatePausing) - waitState(t, timeout, vm, vz.VirtualMachineStatePaused) + if err := waitUntilState(timeout, vm, vz.VirtualMachineStatePaused); err != nil { + t.Fatal(err) + } if got := vm.State(); vz.VirtualMachineStatePaused != got { t.Fatalf("want state %v but got %v", vz.VirtualMachineStatePaused, got) @@ -498,8 +502,9 @@ func TestRunIssue124(t *testing.T) { t.Fatal(err) } - waitState(t, timeout, vm, vz.VirtualMachineStateResuming) - waitState(t, timeout, vm, vz.VirtualMachineStateRunning) + if err := waitUntilState(timeout, vm, vz.VirtualMachineStateRunning); err != nil { + t.Fatal(err) + } if got := vm.CanRequestStop(); !got { t.Fatal("want CanRequestStop is true") From ca686f8162f8a370b2cb85d3932bbb3bce6d491e Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Thu, 7 Nov 2024 20:06:26 +0900 Subject: [PATCH 3/3] fixed issue_test using slice struct --- issues_test.go | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/issues_test.go b/issues_test.go index c029a89d..90b6c983 100644 --- a/issues_test.go +++ b/issues_test.go @@ -49,15 +49,30 @@ func TestIssue50(t *testing.T) { } t.Run("check for segmentation faults", func(t *testing.T) { - cases := map[string]func() error{ - "start handler": func() error { return m.Start() }, - "pause handler": m.Pause, - "resume handler": m.Resume, - "stop handler": m.Stop, + cases := []struct { + name string + run func() error + }{ + { + name: "start handler", + run: func() error { return m.Start() }, + }, + { + name: "pause handler", + run: m.Pause, + }, + { + name: "resume handler", + run: m.Resume, + }, + { + name: "stop handler", + run: m.Stop, + }, } - for name, run := range cases { - t.Run(name, func(t *testing.T) { - _ = run() + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _ = tc.run() }) } })