2020-05-04 14:18:13 -05:00
package runner
import (
2020-11-12 10:15:09 -06:00
"fmt"
2020-11-17 11:31:05 -06:00
"os"
"regexp"
2021-05-04 16:50:35 -05:00
"runtime"
2021-03-12 18:25:10 -06:00
"sort"
2020-11-17 11:31:05 -06:00
"strings"
2020-05-04 14:18:13 -05:00
"testing"
2021-01-18 13:44:27 -06:00
"github.com/nektos/act/pkg/model"
2021-05-06 15:02:29 -05:00
log "github.com/sirupsen/logrus"
2020-05-04 14:18:13 -05:00
"github.com/sirupsen/logrus/hooks/test"
2021-05-18 01:14:49 -05:00
assert "github.com/stretchr/testify/assert"
2020-05-04 14:18:13 -05:00
)
func TestRunContext_EvalBool ( t * testing . T ) {
hook := test . NewGlobal ( )
rc := & RunContext {
Config : & Config {
Workdir : "." ,
} ,
Env : map [ string ] string {
2020-11-17 11:31:05 -06:00
"SOMETHING_TRUE" : "true" ,
"SOMETHING_FALSE" : "false" ,
"SOME_TEXT" : "text" ,
2020-05-04 14:18:13 -05:00
} ,
Run : & model . Run {
JobID : "job1" ,
Workflow : & model . Workflow {
Name : "test-workflow" ,
Jobs : map [ string ] * model . Job {
"job1" : {
Strategy : & model . Strategy {
Matrix : map [ string ] [ ] interface { } {
"os" : { "Linux" , "Windows" } ,
"foo" : { "bar" , "baz" } ,
} ,
} ,
} ,
} ,
} ,
} ,
Matrix : map [ string ] interface { } {
"os" : "Linux" ,
"foo" : "bar" ,
} ,
StepResults : map [ string ] * stepResult {
"id1" : {
Outputs : map [ string ] string {
"foo" : "bar" ,
} ,
Success : true ,
} ,
} ,
}
rc . ExprEval = rc . NewExpressionEvaluator ( )
tables := [ ] struct {
2020-11-17 11:31:05 -06:00
in string
out bool
wantErr bool
2020-05-04 14:18:13 -05:00
} {
// The basic ones
2020-11-17 11:31:05 -06:00
{ in : "failure()" , out : false } ,
{ in : "success()" , out : true } ,
{ in : "cancelled()" , out : false } ,
{ in : "always()" , out : true } ,
{ in : "true" , out : true } ,
{ in : "false" , out : false } ,
{ in : "!true" , wantErr : true } ,
{ in : "!false" , wantErr : true } ,
{ in : "1 != 0" , out : true } ,
{ in : "1 != 1" , out : false } ,
{ in : "${{ 1 != 0 }}" , out : true } ,
{ in : "${{ 1 != 1 }}" , out : false } ,
{ in : "1 == 0" , out : false } ,
{ in : "1 == 1" , out : true } ,
{ in : "1 > 2" , out : false } ,
{ in : "1 < 2" , out : true } ,
2020-05-04 14:18:13 -05:00
// And or
2020-11-17 11:31:05 -06:00
{ in : "true && false" , out : false } ,
{ in : "true && 1 < 2" , out : true } ,
{ in : "false || 1 < 2" , out : true } ,
{ in : "false || false" , out : false } ,
2020-05-04 14:18:13 -05:00
// None boolable
2020-11-17 11:31:05 -06:00
{ in : "env.UNKNOWN == 'true'" , out : false } ,
{ in : "env.UNKNOWN" , out : false } ,
2020-05-04 14:18:13 -05:00
// Inline expressions
2020-11-17 11:31:05 -06:00
{ in : "env.SOME_TEXT" , out : true } , // this is because Boolean('text') is true in Javascript
{ in : "env.SOME_TEXT == 'text'" , out : true } ,
{ in : "env.SOMETHING_TRUE == 'true'" , out : true } ,
{ in : "env.SOMETHING_FALSE == 'true'" , out : false } ,
{ in : "env.SOMETHING_TRUE" , out : true } ,
{ in : "env.SOMETHING_FALSE" , out : true } , // this is because Boolean('text') is true in Javascript
{ in : "!env.SOMETHING_TRUE" , wantErr : true } ,
{ in : "!env.SOMETHING_FALSE" , wantErr : true } ,
{ in : "${{ !env.SOMETHING_TRUE }}" , out : false } ,
{ in : "${{ !env.SOMETHING_FALSE }}" , out : false } ,
{ in : "${{ ! env.SOMETHING_TRUE }}" , out : false } ,
{ in : "${{ ! env.SOMETHING_FALSE }}" , out : false } ,
{ in : "${{ env.SOMETHING_TRUE }}" , out : true } ,
{ in : "${{ env.SOMETHING_FALSE }}" , out : true } ,
{ in : "${{ !env.SOMETHING_TRUE }}" , out : false } ,
{ in : "${{ !env.SOMETHING_FALSE }}" , out : false } ,
{ in : "${{ !env.SOMETHING_TRUE && true }}" , out : false } ,
{ in : "${{ !env.SOMETHING_FALSE && true }}" , out : false } ,
{ in : "${{ !env.SOMETHING_TRUE || true }}" , out : true } ,
{ in : "${{ !env.SOMETHING_FALSE || false }}" , out : false } ,
{ in : "${{ env.SOMETHING_TRUE && true }}" , out : true } ,
{ in : "${{ env.SOMETHING_FALSE || true }}" , out : true } ,
{ in : "${{ env.SOMETHING_FALSE || false }}" , out : true } ,
{ in : "!env.SOMETHING_TRUE || true" , wantErr : true } ,
{ in : "${{ env.SOMETHING_TRUE == 'true'}}" , out : true } ,
{ in : "${{ env.SOMETHING_FALSE == 'true'}}" , out : false } ,
{ in : "${{ env.SOMETHING_FALSE == 'false'}}" , out : true } ,
{ in : "${{ env.SOMETHING_FALSE }} && ${{ env.SOMETHING_TRUE }}" , out : true } ,
2020-11-12 10:15:09 -06:00
2020-05-04 14:18:13 -05:00
// All together now
2020-11-17 11:31:05 -06:00
{ in : "false || env.SOMETHING_TRUE == 'true'" , out : true } ,
{ in : "true || env.SOMETHING_FALSE == 'true'" , out : true } ,
{ in : "true && env.SOMETHING_TRUE == 'true'" , out : true } ,
{ in : "false && env.SOMETHING_TRUE == 'true'" , out : false } ,
{ in : "env.SOMETHING_FALSE == 'true' && env.SOMETHING_TRUE == 'true'" , out : false } ,
{ in : "env.SOMETHING_FALSE == 'true' && true" , out : false } ,
{ in : "${{ env.SOMETHING_FALSE == 'true' }} && true" , out : true } ,
{ in : "true && ${{ env.SOMETHING_FALSE == 'true' }}" , out : true } ,
2020-05-04 14:18:13 -05:00
// Check github context
2020-11-17 11:31:05 -06:00
{ in : "github.actor == 'nektos/act'" , out : true } ,
{ in : "github.actor == 'unknown'" , out : false } ,
2020-11-18 09:14:34 -06:00
// The special ACT flag
{ in : "${{ env.ACT }}" , out : true } ,
{ in : "${{ !env.ACT }}" , out : false } ,
2021-01-18 13:44:27 -06:00
// Invalid expressions should be reported
{ in : "INVALID_EXPRESSION" , wantErr : true } ,
2020-05-04 14:18:13 -05:00
}
2020-11-17 11:31:05 -06:00
updateTestIfWorkflow ( t , tables , rc )
2020-05-04 14:18:13 -05:00
for _ , table := range tables {
table := table
t . Run ( table . in , func ( t * testing . T ) {
2021-05-18 01:14:49 -05:00
assertObject := assert . New ( t )
2020-05-04 14:18:13 -05:00
defer hook . Reset ( )
2020-11-17 11:31:05 -06:00
b , err := rc . EvalBool ( table . in )
if table . wantErr {
2021-05-18 01:14:49 -05:00
assertObject . Error ( err )
2020-11-17 11:31:05 -06:00
}
2020-05-04 14:18:13 -05:00
2021-05-18 01:14:49 -05:00
assertObject . Equal ( table . out , b , fmt . Sprintf ( "Expected %s to be %v, was %v" , table . in , table . out , b ) )
assertObject . Empty ( hook . LastEntry ( ) , table . in )
2020-05-04 14:18:13 -05:00
} )
}
}
2020-11-17 11:31:05 -06:00
func updateTestIfWorkflow ( t * testing . T , tables [ ] struct {
in string
out bool
wantErr bool
} , rc * RunContext ) {
var envs string
2021-03-12 18:25:10 -06:00
keys := make ( [ ] string , 0 , len ( rc . Env ) )
for k := range rc . Env {
keys = append ( keys , k )
2020-11-17 11:31:05 -06:00
}
2021-03-12 18:25:10 -06:00
sort . Strings ( keys )
for _ , k := range keys {
envs += fmt . Sprintf ( " %s: %s\n" , k , rc . Env [ k ] )
}
2020-11-17 11:31:05 -06:00
workflow := fmt . Sprintf ( `
2021-03-30 14:26:25 -05:00
name : "Test what expressions result in true and false on GitHub"
2020-11-17 11:31:05 -06:00
on : push
env :
% s
jobs :
test - ifs - and - buts :
runs - on : ubuntu - latest
steps :
` , envs )
for i , table := range tables {
if table . wantErr || strings . HasPrefix ( table . in , "github.actor" ) {
continue
}
expressionPattern = regexp . MustCompile ( ` \$ {{ \ s * ( . + ? ) \ s * }} ` )
expr := expressionPattern . ReplaceAllStringFunc ( table . in , func ( match string ) string {
return fmt . Sprintf ( "€{{ %s }}" , expressionPattern . ReplaceAllString ( match , "$1" ) )
} )
echo := fmt . Sprintf ( ` run: echo "%s should be false, but was evaluated to true;" exit 1; ` , table . in )
name := fmt . Sprintf ( ` "❌ I should not run, expr: %s" ` , expr )
if table . out {
echo = ` run: echo OK `
name = fmt . Sprintf ( ` "✅ I should run, expr: %s" ` , expr )
}
2021-03-12 18:25:10 -06:00
workflow += fmt . Sprintf ( "\n - name: %s\n id: step%d\n if: %s\n %s\n" , name , i , table . in , echo )
2020-11-17 11:31:05 -06:00
if table . out {
2021-03-12 18:25:10 -06:00
workflow += fmt . Sprintf ( "\n - name: \"Double checking expr: %s\"\n if: steps.step%d.conclusion == 'skipped'\n run: echo \"%s should have been true, but wasn't\"\n" , expr , i , table . in )
2020-11-17 11:31:05 -06:00
}
}
file , err := os . Create ( "../../.github/workflows/test-if.yml" )
if err != nil {
t . Fatal ( err )
}
_ , err = file . WriteString ( workflow )
if err != nil {
t . Fatal ( err )
}
}
2021-05-04 16:50:35 -05:00
func TestRunContext_GetBindsAndMounts ( t * testing . T ) {
rctemplate := & RunContext {
Name : "TestRCName" ,
Run : & model . Run {
Workflow : & model . Workflow {
Name : "TestWorkflowName" ,
} ,
} ,
Config : & Config {
BindWorkdir : false ,
} ,
}
tests := [ ] struct {
windowsPath bool
name string
rc * RunContext
wantbind string
wantmount string
} {
{ false , "/mnt/linux" , rctemplate , "/mnt/linux" , "/mnt/linux" } ,
{ false , "/mnt/path with spaces/linux" , rctemplate , "/mnt/path with spaces/linux" , "/mnt/path with spaces/linux" } ,
{ true , "C:\\Users\\TestPath\\MyTestPath" , rctemplate , "/mnt/c/Users/TestPath/MyTestPath" , "/mnt/c/Users/TestPath/MyTestPath" } ,
{ true , "C:\\Users\\Test Path with Spaces\\MyTestPath" , rctemplate , "/mnt/c/Users/Test Path with Spaces/MyTestPath" , "/mnt/c/Users/Test Path with Spaces/MyTestPath" } ,
{ true , "/LinuxPathOnWindowsShouldFail" , rctemplate , "" , "" } ,
}
isWindows := runtime . GOOS == "windows"
for _ , testcase := range tests {
// pin for scopelint
testcase := testcase
for _ , bindWorkDir := range [ ] bool { true , false } {
// pin for scopelint
bindWorkDir := bindWorkDir
testBindSuffix := ""
if bindWorkDir {
testBindSuffix = "Bind"
}
// Only run windows path tests on windows and non-windows on non-windows
if ( isWindows && testcase . windowsPath ) || ( ! isWindows && ! testcase . windowsPath ) {
t . Run ( ( testcase . name + testBindSuffix ) , func ( t * testing . T ) {
config := testcase . rc . Config
config . Workdir = testcase . name
config . BindWorkdir = bindWorkDir
gotbind , gotmount := rctemplate . GetBindsAndMounts ( )
// Name binds/mounts are either/or
if config . BindWorkdir {
fullBind := testcase . name + ":" + testcase . wantbind
if runtime . GOOS == "darwin" {
fullBind += ":delegated"
}
2021-05-18 01:14:49 -05:00
assert . Contains ( t , gotbind , fullBind )
2021-05-04 16:50:35 -05:00
} else {
mountkey := testcase . rc . jobContainerName ( )
2021-05-18 01:14:49 -05:00
assert . EqualValues ( t , testcase . wantmount , gotmount [ mountkey ] )
2021-05-04 16:50:35 -05:00
}
} )
}
}
}
}
2021-05-06 15:02:29 -05:00
func TestGetGitHubContext ( t * testing . T ) {
log . SetLevel ( log . DebugLevel )
cwd , err := os . Getwd ( )
2021-05-18 01:14:49 -05:00
assert . Nil ( t , err )
2021-05-06 15:02:29 -05:00
rc := & RunContext {
Config : & Config {
EventName : "push" ,
Workdir : cwd ,
} ,
Run : & model . Run {
Workflow : & model . Workflow {
Name : "GitHubContextTest" ,
} ,
} ,
Name : "GitHubContextTest" ,
CurrentStep : "step" ,
Matrix : map [ string ] interface { } { } ,
Env : map [ string ] string { } ,
ExtraPath : [ ] string { } ,
StepResults : map [ string ] * stepResult { } ,
OutputMappings : map [ MappableOutput ] MappableOutput { } ,
}
ghc := rc . getGithubContext ( )
log . Debugf ( "%v" , ghc )
assert . Equal ( t , ghc . RunID , "1" )
assert . Equal ( t , ghc . Workspace , cwd )
assert . Equal ( t , ghc . RunNumber , "1" )
assert . Equal ( t , ghc . RetentionDays , "0" )
assert . Equal ( t , ghc . Actor , "nektos/act" )
assert . Equal ( t , ghc . Repository , "nektos/act" )
assert . Equal ( t , ghc . RepositoryOwner , "nektos" )
assert . Equal ( t , ghc . RunnerPerflog , "/dev/null" )
assert . Equal ( t , ghc . EventPath , "/tmp/workflow/event.json" )
assert . Equal ( t , ghc . Token , rc . Config . Secrets [ "GITHUB_TOKEN" ] )
}