@@ -6,13 +6,20 @@ import (
66 "context"
77 "encoding/json"
88 "io/fs"
9+ "os"
10+ "os/exec"
11+ "strconv"
12+ "sync"
913 "testing"
1014 "time"
1115
16+ log "github.com/sirupsen/logrus"
17+ logtest "github.com/sirupsen/logrus/hooks/test"
1218 "github.com/spf13/afero"
1319 "github.com/stretchr/testify/assert"
1420 "go.uber.org/mock/gomock"
1521
22+ "github.com/netapp/trident/logging"
1623 "github.com/netapp/trident/mocks/mock_utils/mock_exec"
1724 "github.com/netapp/trident/utils/errors"
1825 execCmd "github.com/netapp/trident/utils/exec"
@@ -25,6 +32,8 @@ type MockFs struct {
2532 afero.MemMapFs
2633}
2734
35+ var globalLogMutationMutex sync.Mutex
36+
2837func (m * MockFs ) Open (name string ) (afero.File , error ) {
2938 return nil , fs .ErrPermission
3039}
@@ -343,6 +352,43 @@ func TestFormatVolumeRetry(t *testing.T) {
343352 }
344353}
345354
355+ func newExitErrorForCode (t * testing.T , code int ) error {
356+ t .Helper ()
357+ exePath , err := os .Executable ()
358+ assert .NoError (t , err )
359+
360+ // Spawn this test binary as a helper process and force it to exit with the requested code.
361+ // This gives us a real *exec.ExitError in a cross-platform way (no shell dependency).
362+ cmd := exec .Command (exePath , "-test.run=TestExitWithCodeHelperProcess" , "--" , strconv .Itoa (code ))
363+ // Gate helper mode so normal test runs don't execute the forced os.Exit path.
364+ cmd .Env = append (os .Environ (), "GO_WANT_HELPER_PROCESS=1" )
365+ err = cmd .Run ()
366+ assert .Error (t , err )
367+ var exitErr * exec.ExitError
368+ assert .ErrorAs (t , err , & exitErr )
369+ return err
370+ }
371+
372+ func TestExitWithCodeHelperProcess (t * testing.T ) {
373+ if os .Getenv ("GO_WANT_HELPER_PROCESS" ) != "1" {
374+ return
375+ }
376+
377+ for i , arg := range os .Args {
378+ if arg != "--" || i + 1 >= len (os .Args ) {
379+ continue
380+ }
381+
382+ code , err := strconv .Atoi (os .Args [i + 1 ])
383+ if err != nil {
384+ os .Exit (2 )
385+ }
386+ os .Exit (code )
387+ }
388+
389+ os .Exit (2 )
390+ }
391+
346392func TestRepairVolume (t * testing.T ) {
347393 mockCtrl := gomock .NewController (t )
348394 mockExec := mock_exec .NewMockCommand (mockCtrl )
@@ -352,6 +398,9 @@ func TestRepairVolume(t *testing.T) {
352398 device string
353399 fstype string
354400 mockSetup func ()
401+ logLevel log.Level
402+ exitCode * int
403+ reason string
355404 }
356405 tests := map [string ]parameters {
357406 "fsck.xfs does nothing" : {
@@ -377,20 +426,74 @@ func TestRepairVolume(t *testing.T) {
377426 device : "/dev/sda1" ,
378427 fstype : "unsupported" ,
379428 mockSetup : func () {},
429+ logLevel : log .ErrorLevel ,
430+ reason : "unsupported_fstype" ,
380431 },
381432 "Error executing fsck" : {
382433 device : "/dev/sda1" ,
383434 fstype : Ext4 ,
384435 mockSetup : func () {
385436 mockExec .EXPECT ().Execute (gomock .Any (), "fsck.ext4" , "-p" , "/dev/sda1" ).Return (nil , errors .New ("mock error" )).Times (1 )
386437 },
438+ logLevel : log .ErrorLevel ,
439+ reason : "fsck_exec_error" ,
440+ },
441+ "Unexpected fsck exit code is handled" : {
442+ device : "/dev/sda1" ,
443+ fstype : Ext4 ,
444+ mockSetup : func () {
445+ mockExec .EXPECT ().Execute (
446+ gomock .Any (), "fsck.ext4" , "-p" , "/dev/sda1" ,
447+ ).Return (nil , newExitErrorForCode (t , 42 )).Times (1 )
448+ },
449+ logLevel : log .ErrorLevel ,
450+ exitCode : func () * int { v := 42 ; return & v }(),
451+ reason : "fsck_unexpected_exit_code" ,
387452 },
388453 }
389454
390455 for name , params := range tests {
391456 t .Run (name , func (t * testing.T ) {
457+ globalLogMutationMutex .Lock ()
458+ originalDefaultLevel := logging .GetDefaultLogLevel ()
459+ hook := logtest .NewGlobal ()
460+ assert .NoError (t , logging .SetDefaultLogLevel ("trace" ))
461+ t .Cleanup (func () {
462+ hook .Reset ()
463+ assert .NoError (t , logging .SetDefaultLogLevel (originalDefaultLevel ))
464+ globalLogMutationMutex .Unlock ()
465+ })
466+
392467 params .mockSetup ()
393468 fsClient .RepairVolume (context .Background (), params .device , params .fstype )
469+ if params .exitCode != nil || params .reason != "" {
470+ found := false
471+ for _ , e := range hook .AllEntries () {
472+ if e .Level != params .logLevel {
473+ continue
474+ }
475+ if params .exitCode != nil {
476+ rawCode , ok := e .Data ["exitCode" ]
477+ if ! ok {
478+ continue
479+ }
480+ assert .Equal (t , * params .exitCode , rawCode )
481+ }
482+ if params .reason != "" {
483+ rawReason , ok := e .Data ["reason" ]
484+ if ! ok {
485+ continue
486+ }
487+ assert .Equal (t , params .reason , rawReason )
488+ }
489+ found = true
490+ break
491+ }
492+ assert .True (
493+ t , found , "expected matching log criteria level=%s exitCode=%v reason=%q, got entries: %#v" ,
494+ params .logLevel , params .exitCode , params .reason , hook .AllEntries (),
495+ )
496+ }
394497 })
395498 }
396499}
0 commit comments