unit tests

Signed-off-by: Casey Lee <cplee@nektos.com>
This commit is contained in:
Casey Lee
2020-02-10 15:27:05 -08:00
parent be75ee20b1
commit 0582306861
466 changed files with 94683 additions and 62526 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
@@ -182,20 +183,19 @@ func findGitDirectory(fromFile string) (string, error) {
// NewGitCloneExecutorInput the input for the NewGitCloneExecutor
type NewGitCloneExecutorInput struct {
URL string
Ref string
Dir string
Logger *log.Entry
Dryrun bool
URL string
Ref string
Dir string
}
// NewGitCloneExecutor creates an executor to clone git repos
func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
return func(ctx context.Context) error {
input.Logger.Infof("git clone '%s' # ref=%s", input.URL, input.Ref)
input.Logger.Debugf(" cloning %s to %s", input.URL, input.Dir)
logger := Logger(ctx)
logger.Infof("git clone '%s' # ref=%s", input.URL, input.Ref)
logger.Debugf(" cloning %s to %s", input.URL, input.Dir)
if input.Dryrun {
if Dryrun(ctx) {
return nil
}
@@ -206,15 +206,26 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
r, err := git.PlainOpen(input.Dir)
if err != nil {
var progressWriter io.Writer
if entry, ok := logger.(*log.Entry); ok {
progressWriter = entry.WriterLevel(log.DebugLevel)
} else if lgr, ok := logger.(*log.Logger); ok {
progressWriter = lgr.WriterLevel(log.DebugLevel)
} else {
log.Errorf("Unable to get writer from logger (type=%T)", logger)
progressWriter = os.Stdout
}
r, err = git.PlainClone(input.Dir, false, &git.CloneOptions{
URL: input.URL,
Progress: input.Logger.WriterLevel(log.DebugLevel),
Progress: progressWriter,
//ReferenceName: refName,
})
if err != nil {
input.Logger.Errorf("Unable to clone %v %s: %v", input.URL, refName, err)
logger.Errorf("Unable to clone %v %s: %v", input.URL, refName, err)
return err
}
os.Chmod(input.Dir, 0755)
}
w, err := r.Worktree()
@@ -227,13 +238,13 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
Force: true,
})
if err != nil && err.Error() != "already up-to-date" {
input.Logger.Errorf("Unable to pull %s: %v", refName, err)
logger.Errorf("Unable to pull %s: %v", refName, err)
}
input.Logger.Debugf("Cloned %s to %s", input.URL, input.Dir)
logger.Debugf("Cloned %s to %s", input.URL, input.Dir)
hash, err := r.ResolveRevision(plumbing.Revision(input.Ref))
if err != nil {
input.Logger.Errorf("Unable to resolve %s: %v", input.Ref, err)
logger.Errorf("Unable to resolve %s: %v", input.Ref, err)
return err
}
@@ -243,11 +254,11 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
Force: true,
})
if err != nil {
input.Logger.Errorf("Unable to checkout %s: %v", refName, err)
logger.Errorf("Unable to checkout %s: %v", refName, err)
return err
}
input.Logger.Debugf("Checked out %s", input.Ref)
logger.Debugf("Checked out %s", input.Ref)
return nil
}
}

View File

@@ -40,7 +40,8 @@ func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor {
tags := []string{input.ImageTag}
options := types.ImageBuildOptions{
Tags: tags,
Tags: tags,
Remove: true,
}
buildContext, err := createBuildContext(input.ContextDir, "Dockerfile")

View File

@@ -19,27 +19,28 @@ func TestImageExistsLocally(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
ctx := context.Background()
// to help make this test reliable and not flaky, we need to have
// an image that will exist, and onew that won't exist
exists, err := ImageExistsLocally(context.TODO(), "library/alpine:this-random-tag-will-never-exist")
exists, err := ImageExistsLocally(ctx, "library/alpine:this-random-tag-will-never-exist")
assert.Nil(t, err)
assert.Equal(t, false, exists)
// pull an image
cli, err := client.NewClientWithOpts(client.FromEnv)
assert.Nil(t, err)
cli.NegotiateAPIVersion(context.TODO())
cli.NegotiateAPIVersion(context.Background())
// Chose alpine latest because it's so small
// maybe we should build an image instead so that tests aren't reliable on dockerhub
reader, err := cli.ImagePull(context.TODO(), "alpine:latest", types.ImagePullOptions{})
reader, err := cli.ImagePull(ctx, "alpine:latest", types.ImagePullOptions{})
assert.Nil(t, err)
defer reader.Close()
_, err = ioutil.ReadAll(reader)
assert.Nil(t, err)
exists, err = ImageExistsLocally(context.TODO(), "alpine:latest")
exists, err = ImageExistsLocally(ctx, "alpine:latest")
assert.Nil(t, err)
assert.Equal(t, true, exists)
}

View File

@@ -39,6 +39,7 @@ type ContainerSpec struct {
Options string `yaml:"options"`
Entrypoint string
Args string
Name string
}
// Step is the structure of one step in a job
@@ -63,8 +64,8 @@ func (s *Step) GetEnv() map[string]string {
rtnEnv[k] = v
}
for k, v := range s.With {
envKey := fmt.Sprintf("INPUT_%s", strings.ToUpper(k))
envKey = regexp.MustCompile("[^A-Z0-9]").ReplaceAllString(envKey, "_")
envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(k), "_")
envKey = fmt.Sprintf("INPUT_%s", strings.ToUpper(envKey))
rtnEnv[envKey] = v
}
return rtnEnv

View File

@@ -1,5 +0,0 @@
package runner
type environmentApplier interface {
applyEnvironment(map[string]string)
}

View File

@@ -45,10 +45,13 @@ func (rc *RunContext) Close(ctx context.Context) error {
// Executor returns a pipeline executor for all the steps in the job
func (rc *RunContext) Executor() common.Executor {
rc.setupTempDir()
steps := make([]common.Executor, 0)
steps = append(steps, rc.setupTempDir())
for _, step := range rc.Run.Job().Steps {
for i, step := range rc.Run.Job().Steps {
if step.ID == "" {
step.ID = fmt.Sprintf("%d", i)
}
steps = append(steps, rc.newStepExecutor(step))
}
return common.NewPipelineExecutor(steps...).Finally(rc.Close)
@@ -64,17 +67,16 @@ func mergeMaps(maps ...map[string]string) map[string]string {
return rtnMap
}
func (rc *RunContext) setupTempDir() common.Executor {
return func(ctx context.Context) error {
var err error
tempBase := ""
if runtime.GOOS == "darwin" {
tempBase = "/tmp"
}
rc.Tempdir, err = ioutil.TempDir(tempBase, "act-")
log.Debugf("Setup tempdir %s", rc.Tempdir)
return err
func (rc *RunContext) setupTempDir() error {
var err error
tempBase := ""
if runtime.GOOS == "darwin" {
tempBase = "/tmp"
}
rc.Tempdir, err = ioutil.TempDir(tempBase, "act-")
os.Chmod(rc.Tempdir, 0755)
log.Debugf("Setup tempdir %s", rc.Tempdir)
return err
}
func (rc *RunContext) pullImage(containerSpec *model.ContainerSpec) common.Executor {
@@ -111,7 +113,7 @@ func (rc *RunContext) runContainer(containerSpec *model.ContainerSpec) common.Ex
Image: containerSpec.Image,
WorkingDir: "/github/workspace",
Env: envList,
Name: rc.createContainerName(),
Name: containerSpec.Name,
Binds: []string{
fmt.Sprintf("%s:%s", rc.Config.Workdir, "/github/workspace"),
fmt.Sprintf("%s:%s", rc.Tempdir, "/github/home"),
@@ -155,8 +157,9 @@ func (rc *RunContext) createGithubTarball() (io.Reader, error) {
}
func (rc *RunContext) createContainerName() string {
containerName := regexp.MustCompile("[^a-zA-Z0-9]").ReplaceAllString(rc.Run.String(), "-")
func (rc *RunContext) createContainerName(stepID string) string {
containerName := fmt.Sprintf("%s-%s", stepID, rc.Tempdir)
containerName = regexp.MustCompile("[^a-zA-Z0-9]").ReplaceAllString(containerName, "-")
prefix := fmt.Sprintf("%s-", trimToLen(filepath.Base(rc.Config.Workdir), 10))
suffix := ""

View File

@@ -2,28 +2,29 @@ package runner
import (
"context"
"fmt"
"testing"
"github.com/nektos/act/pkg/model"
log "github.com/sirupsen/logrus"
"gotest.tools/assert"
)
func TestGraphEvent(t *testing.T) {
runnerConfig := &Config{
WorkflowPath: "multi.workflow",
WorkingDir: "testdata",
EventName: "push",
}
runner, err := NewRunner(runnerConfig)
planner, err := model.NewWorkflowPlanner("testdata/basic")
assert.NilError(t, err)
graph, err := runner.GraphEvent("push")
plan := planner.PlanEvent("push")
assert.NilError(t, err)
assert.DeepEqual(t, graph, [][]string{{"build"}})
assert.Equal(t, len(plan.Stages), 2, "stages")
assert.Equal(t, len(plan.Stages[0].Runs), 1, "stage0.runs")
assert.Equal(t, len(plan.Stages[1].Runs), 1, "stage1.runs")
assert.Equal(t, plan.Stages[0].Runs[0].JobID, "build", "jobid")
assert.Equal(t, plan.Stages[1].Runs[0].JobID, "test", "jobid")
graph, err = runner.GraphEvent("release")
assert.NilError(t, err)
assert.DeepEqual(t, graph, [][]string{{"deploy"}})
plan = planner.PlanEvent("release")
assert.Equal(t, len(plan.Stages), 0, "stages")
}
func TestRunEvent(t *testing.T) {
@@ -36,30 +37,36 @@ func TestRunEvent(t *testing.T) {
eventName string
errorMessage string
}{
{"basic.workflow", "push", ""},
{"pipe.workflow", "push", ""},
{"fail.workflow", "push", "exit with `FAILURE`: 1"},
{"buildfail.workflow", "push", "COPY failed"},
{"regex.workflow", "push", "exit with `NEUTRAL`: 78"},
{"gitref.workflow", "push", ""},
{"env.workflow", "push", ""},
{"detect_event.workflow", "", ""},
{"basic", "push", ""},
{"fail", "push", "exit with `FAILURE`: 1"},
{"runs-on", "push", ""},
{"job-container", "push", ""},
{"uses-docker-url", "push", ""},
{"remote-action-docker", "push", ""},
{"remote-action-js", "push", ""},
{"local-action-docker-url", "push", ""},
{"local-action-dockerfile", "push", ""},
}
log.SetLevel(log.DebugLevel)
ctx := context.Background()
for _, table := range tables {
table := table
t.Run(table.workflowPath, func(t *testing.T) {
runnerConfig := &RunnerConfig{
Ctx: context.Background(),
WorkflowPath: table.workflowPath,
WorkingDir: "testdata",
EventName: table.eventName,
runnerConfig := &Config{
Workdir: "testdata",
EventName: table.eventName,
}
runner, err := NewRunner(runnerConfig)
runner, err := New(runnerConfig)
assert.NilError(t, err, table.workflowPath)
err = runner.RunEvent()
planner, err := model.NewWorkflowPlanner(fmt.Sprintf("testdata/%s", table.workflowPath))
assert.NilError(t, err, table.workflowPath)
plan := planner.PlanEvent(table.eventName)
err = runner.NewPlanExecutor(plan)(ctx)
if table.errorMessage == "" {
assert.NilError(t, err, table.workflowPath)
} else {

View File

@@ -10,6 +10,7 @@ import (
"strings"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/model"
log "github.com/sirupsen/logrus"
)
@@ -18,6 +19,7 @@ func (rc *RunContext) newStepExecutor(step *model.Step) common.Executor {
job := rc.Run.Job()
containerSpec := new(model.ContainerSpec)
containerSpec.Env = rc.StepEnv(step)
containerSpec.Name = rc.createContainerName(step.ID)
switch step.Type() {
case model.StepTypeRun:
@@ -45,15 +47,26 @@ func (rc *RunContext) newStepExecutor(step *model.Step) common.Executor {
)
case model.StepTypeUsesActionLocal:
containerSpec.Image = fmt.Sprintf("%s:%s", containerSpec.Name, "latest")
return common.NewPipelineExecutor(
rc.setupAction(containerSpec, filepath.Join(rc.Config.Workdir, step.Uses)),
rc.pullImage(containerSpec),
rc.runContainer(containerSpec),
)
case model.StepTypeUsesActionRemote:
remoteAction := newRemoteAction(step.Uses)
cloneDir, err := ioutil.TempDir(rc.Tempdir, remoteAction.Repo)
if err != nil {
return common.NewErrorExecutor(err)
}
containerSpec.Image = fmt.Sprintf("%s:%s", remoteAction.Repo, remoteAction.Ref)
return common.NewPipelineExecutor(
rc.cloneAction(step.Uses),
rc.setupAction(containerSpec, step.Uses),
common.NewGitCloneExecutor(common.NewGitCloneExecutorInput{
URL: remoteAction.CloneURL(),
Ref: remoteAction.Ref,
Dir: cloneDir,
}),
rc.setupAction(containerSpec, filepath.Join(cloneDir, remoteAction.Path)),
rc.pullImage(containerSpec),
rc.runContainer(containerSpec),
)
@@ -174,8 +187,8 @@ func (rc *RunContext) setupAction(containerSpec *model.ContainerSpec, actionDir
}
for inputID, input := range action.Inputs {
envKey := fmt.Sprintf("INPUT_%s", strings.ToUpper(inputID))
envKey = regexp.MustCompile("[^A-Z0-9]").ReplaceAllString(envKey, "_")
envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(inputID), "_")
envKey = fmt.Sprintf("INPUT_%s", envKey)
if _, ok := containerSpec.Env[envKey]; !ok {
containerSpec.Env[envKey] = input.Default
}
@@ -183,23 +196,54 @@ func (rc *RunContext) setupAction(containerSpec *model.ContainerSpec, actionDir
switch action.Runs.Using {
case model.ActionRunsUsingNode12:
containerSpec.Image = "node:12"
containerSpec.Args = action.Runs.Main
containerSpec.Image = "node:12-alpine"
if strings.HasPrefix(actionDir, rc.Config.Workdir) {
containerSpec.Args = fmt.Sprintf("node /github/workspace/%s/%s", strings.TrimPrefix(actionDir, rc.Config.Workdir), action.Runs.Main)
} else if strings.HasPrefix(actionDir, rc.Tempdir) {
containerSpec.Args = fmt.Sprintf("node /github/home/%s/%s", strings.TrimPrefix(actionDir, rc.Tempdir), action.Runs.Main)
}
case model.ActionRunsUsingDocker:
if strings.HasPrefix(action.Runs.Image, "docker://") {
containerSpec.Image = strings.TrimPrefix(action.Runs.Image, "docker://")
containerSpec.Entrypoint = strings.Join(action.Runs.Entrypoint, " ")
containerSpec.Args = strings.Join(action.Runs.Args, " ")
} else {
// TODO: docker build
contextDir := filepath.Join(actionDir, action.Runs.Main)
return container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
ContextDir: contextDir,
ImageTag: containerSpec.Image,
})(ctx)
}
}
return nil
}
}
func (rc *RunContext) cloneAction(action string) common.Executor {
return func(ctx context.Context) error {
return nil
}
type remoteAction struct {
Org string
Repo string
Path string
Ref string
}
func (ra *remoteAction) CloneURL() string {
return fmt.Sprintf("https://github.com/%s/%s", ra.Org, ra.Repo)
}
func newRemoteAction(action string) *remoteAction {
r := regexp.MustCompile(`^([^/@]+)/([^/@]+)(/([^@]*))?(@(.*))?$`)
matches := r.FindStringSubmatch(action)
ra := new(remoteAction)
ra.Org = matches[1]
ra.Repo = matches[2]
ra.Path = ""
ra.Ref = "master"
if len(matches) >= 5 {
ra.Path = matches[4]
}
if len(matches) >= 7 {
ra.Ref = matches[6]
}
return ra
}

View File

@@ -0,0 +1 @@
FROM ubuntu:18.04

View File

@@ -0,0 +1,4 @@
name: 'action1'
runs:
using: 'docker'
image: 'Dockerfile'

View File

@@ -0,0 +1,8 @@
# Container image that runs your code
FROM alpine:3.10
# Copies your code file from your action repository to the filesystem path `/` of the container
COPY entrypoint.sh /entrypoint.sh
# Code file to execute when the docker container starts up (`entrypoint.sh`)
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -0,0 +1,15 @@
name: 'Hello World'
description: 'Greet someone and record the time'
inputs:
who-to-greet: # id of input
description: 'Who to greet'
required: true
default: 'World'
outputs:
time: # id of output
description: 'The time we greeted you'
runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.who-to-greet }}

View File

@@ -0,0 +1,5 @@
#!/bin/sh -l
echo "Hello $1"
time=$(date)
echo ::set-output name=time::$time

View File

@@ -0,0 +1,16 @@
name: docker-url
author: nektos
description: testing
inputs:
who-to-greet:
description: who to greet
required: true
default: World
runs:
using: docker
#image: docker://alpine:3.8
image: docker://node:12-alpine
env:
TEST: enabled
args:
- env

17
pkg/runner/testdata/basic/push.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: basic
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: ./actions/action1
with:
args: echo 'build'
test:
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: docker://ubuntu:18.04
with:
args: echo ${GITHUB_REF} | grep nektos/act

12
pkg/runner/testdata/fail/push.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: fail
on: push
jobs:
build:
runs-on: ubuntu-latest
container:
image: node:10.16-jessie
env:
TEST_ENV: test-value
steps:
- run: echo ${TEST_ENV} | grep bazooka

View File

@@ -0,0 +1,12 @@
name: job-container
on: push
jobs:
test:
runs-on: ubuntu-latest
container:
image: node:10.16-jessie
env:
TEST_ENV: test-value
steps:
- run: echo ${TEST_ENV} | grep test-value

View File

@@ -0,0 +1,8 @@
name: local-action-docker-url
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: ./actions/docker-url

View File

@@ -0,0 +1,10 @@
name: local-action-dockerfile
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: ./actions/docker-local
with:
who-to-greet: 'Mona the Octocat'

View File

@@ -0,0 +1,10 @@
name: remote-action-docker
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/hello-world-docker-action@master
with:
who-to-greet: 'Mona the Octocat'

View File

@@ -0,0 +1,10 @@
name: remote-action-js
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/hello-world-javascript-action@master
with:
who-to-greet: 'Mona the Octocat'

8
pkg/runner/testdata/runs-on/push.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
name: runs-on
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo ${GITHUB_ACTOR} | grep nektos/act

View File

@@ -0,0 +1,11 @@
name: uses-docker-url
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: docker://alpine:3.8
with:
somekey: somevalue
args: echo ${INPUT_SOMEKEY} | grep somevalue