2021-12-30 13:11:58 +11:00
package oscommands
import (
"bufio"
"fmt"
2022-01-03 15:15:26 +11:00
"regexp"
2023-05-21 17:00:29 +10:00
"runtime"
2021-12-30 13:11:58 +11:00
"strings"
2023-07-29 12:32:59 +10:00
"sync"
2021-12-30 13:11:58 +11:00
"testing"
"github.com/go-errors/errors"
2023-07-29 12:32:59 +10:00
"github.com/samber/lo"
"golang.org/x/exp/slices"
2021-12-30 13:11:58 +11:00
)
2021-12-30 13:35:10 +11:00
// for use in testing
2021-12-30 13:11:58 +11:00
type FakeCmdObjRunner struct {
2023-07-29 12:32:59 +10:00
t * testing . T
// commands can be run in any order; mimicking the concurrent behaviour of
// production code.
expectedCmds [ ] CmdObjMatcher
invokedCmdIndexes [ ] int
mutex sync . Mutex
}
type CmdObjMatcher struct {
description string
// returns true if the matcher matches the command object
test func ( ICmdObj ) bool
// output of the command
output string
// error of the command
err error
2021-12-30 13:11:58 +11:00
}
var _ ICmdObjRunner = & FakeCmdObjRunner { }
2022-03-19 09:38:49 +11:00
func NewFakeRunner ( t * testing . T ) * FakeCmdObjRunner { //nolint:thelper
2021-12-30 13:11:58 +11:00
return & FakeCmdObjRunner { t : t }
}
2023-07-29 12:32:59 +10:00
func ( self * FakeCmdObjRunner ) remainingExpectedCmds ( ) [ ] CmdObjMatcher {
return lo . Filter ( self . expectedCmds , func ( _ CmdObjMatcher , i int ) bool {
return ! lo . Contains ( self . invokedCmdIndexes , i )
} )
}
2021-12-30 13:11:58 +11:00
func ( self * FakeCmdObjRunner ) Run ( cmdObj ICmdObj ) error {
_ , err := self . RunWithOutput ( cmdObj )
return err
}
func ( self * FakeCmdObjRunner ) RunWithOutput ( cmdObj ICmdObj ) ( string , error ) {
2023-07-29 12:32:59 +10:00
self . mutex . Lock ( )
defer self . mutex . Unlock ( )
if len ( self . remainingExpectedCmds ( ) ) == 0 {
2021-12-30 13:11:58 +11:00
self . t . Errorf ( "ran too many commands. Unexpected command: `%s`" , cmdObj . ToString ( ) )
return "" , errors . New ( "ran too many commands" )
}
2023-07-29 12:32:59 +10:00
for i := range self . expectedCmds {
if lo . Contains ( self . invokedCmdIndexes , i ) {
continue
}
expectedCmd := self . expectedCmds [ i ]
matched := expectedCmd . test ( cmdObj )
if matched {
self . invokedCmdIndexes = append ( self . invokedCmdIndexes , i )
return expectedCmd . output , expectedCmd . err
}
}
2021-12-30 13:11:58 +11:00
2023-07-29 12:32:59 +10:00
self . t . Errorf ( "Unexpected command: `%s`" , cmdObj . ToString ( ) )
return "" , nil
2021-12-30 13:11:58 +11:00
}
2022-08-02 08:32:28 +09:00
func ( self * FakeCmdObjRunner ) RunWithOutputs ( cmdObj ICmdObj ) ( string , string , error ) {
output , err := self . RunWithOutput ( cmdObj )
return output , "" , err
}
2021-12-30 13:11:58 +11:00
func ( self * FakeCmdObjRunner ) RunAndProcessLines ( cmdObj ICmdObj , onLine func ( line string ) ( bool , error ) ) error {
output , err := self . RunWithOutput ( cmdObj )
if err != nil {
return err
}
scanner := bufio . NewScanner ( strings . NewReader ( output ) )
scanner . Split ( bufio . ScanLines )
for scanner . Scan ( ) {
line := scanner . Text ( )
stop , err := onLine ( line )
if err != nil {
return err
}
if stop {
break
}
}
return nil
}
2023-07-29 12:32:59 +10:00
func ( self * FakeCmdObjRunner ) ExpectFunc ( description string , fn func ( cmdObj ICmdObj ) bool , output string , err error ) * FakeCmdObjRunner {
self . mutex . Lock ( )
defer self . mutex . Unlock ( )
2021-12-30 17:19:01 +11:00
2023-07-29 12:32:59 +10:00
self . expectedCmds = append ( self . expectedCmds , CmdObjMatcher {
test : fn ,
output : output ,
err : err ,
description : description ,
2021-12-30 17:19:01 +11:00
} )
return self
}
func ( self * FakeCmdObjRunner ) ExpectArgs ( expectedArgs [ ] string , output string , err error ) * FakeCmdObjRunner {
2023-07-29 12:32:59 +10:00
description := fmt . Sprintf ( "matches args %s" , strings . Join ( expectedArgs , " " ) )
self . ExpectFunc ( description , func ( cmdObj ICmdObj ) bool {
2021-12-30 17:19:01 +11:00
args := cmdObj . GetCmd ( ) . Args
2023-05-21 17:00:29 +10:00
if runtime . GOOS == "windows" {
// thanks to the secureexec package, the first arg is something like
// '"C:\\Program Files\\Git\\mingw64\\bin\\<command>.exe"
// on windows so we'll just ensure it contains our program
2023-07-29 12:32:59 +10:00
if ! strings . Contains ( args [ 0 ] , expectedArgs [ 0 ] ) {
return false
}
2023-05-21 17:00:29 +10:00
} else {
// first arg is the program name
2023-07-29 12:32:59 +10:00
if expectedArgs [ 0 ] != args [ 0 ] {
return false
}
2023-05-21 17:00:29 +10:00
}
2023-07-29 12:32:59 +10:00
if ! slices . Equal ( expectedArgs [ 1 : ] , args [ 1 : ] ) {
return false
}
2021-12-30 13:11:58 +11:00
2023-07-29 12:32:59 +10:00
return true
} , output , err )
2021-12-30 13:11:58 +11:00
return self
}
2021-12-30 17:19:01 +11:00
2022-01-03 15:15:26 +11:00
func ( self * FakeCmdObjRunner ) ExpectGitArgs ( expectedArgs [ ] string , output string , err error ) * FakeCmdObjRunner {
2023-07-29 12:32:59 +10:00
description := fmt . Sprintf ( "matches git args %s" , strings . Join ( expectedArgs , " " ) )
self . ExpectFunc ( description , func ( cmdObj ICmdObj ) bool {
2022-01-03 15:15:26 +11:00
// first arg is 'git' on unix and something like '"C:\\Program Files\\Git\\mingw64\\bin\\git.exe" on windows so we'll just ensure it ends in either 'git' or 'git.exe'
re := regexp . MustCompile ( ` git(\.exe)?$ ` )
args := cmdObj . GetCmd ( ) . Args
if ! re . MatchString ( args [ 0 ] ) {
2023-07-29 12:32:59 +10:00
return false
2022-01-03 15:15:26 +11:00
}
2023-07-29 12:32:59 +10:00
if ! slices . Equal ( expectedArgs , args [ 1 : ] ) {
return false
}
return true
} , output , err )
2022-01-03 15:15:26 +11:00
return self
}
2021-12-30 17:19:01 +11:00
func ( self * FakeCmdObjRunner ) CheckForMissingCalls ( ) {
2023-07-29 12:32:59 +10:00
self . mutex . Lock ( )
defer self . mutex . Unlock ( )
remaining := self . remainingExpectedCmds ( )
if len ( remaining ) > 0 {
self . t . Errorf (
"expected %d more command(s) to be run. Remaining commands:\n%s" ,
len ( remaining ) ,
strings . Join (
lo . Map ( remaining , func ( cmdObj CmdObjMatcher , _ int ) string {
return cmdObj . description
} ) ,
"\n" ,
) ,
)
2021-12-30 17:19:01 +11:00
}
}