Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions internal/campaigns/executor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package campaigns

import (
"archive/zip"
"bytes"
"context"
"fmt"
"io/ioutil"
"log"
"mime"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"runtime"
"testing"
"time"

"github.com/sourcegraph/go-diff/diff"
"github.com/sourcegraph/src-cli/internal/api"
"github.com/sourcegraph/src-cli/internal/campaigns/graphql"
)

func TestExecutor_Integration(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Test doesn't work on Windows because dummydocker is written in bash")
}

addToPath(t, "testdata/dummydocker")

repo := &graphql.Repository{
Name: "github.com/sourcegraph/src-cli",
DefaultBranch: &graphql.Branch{
Name: "main",
Target: struct{ OID string }{OID: "d34db33f"},
},
}

filesInRepo := map[string]string{
"README.md": "# Welcome to the README\n",
"main.go": "package main\n\nfunc main() {\n\tfmt.Println( \"Hello World\")\n}\n",
}

steps := []Step{
{Run: `echo -e "foobar\n" >> README.md`, Container: "alpine:13"},
{Run: `go fmt main.go`, Container: "doesntmatter:13"},
}

h := newZipHandler(t, repo.Name, repo.DefaultBranch.Name, filesInRepo)
ts := httptest.NewServer(h)
defer ts.Close()

var clientBuffer bytes.Buffer
client := api.NewClient(api.ClientOpts{Endpoint: ts.URL, Out: &clientBuffer})

testTempDir, err := ioutil.TempDir("", "executor-integration-test-*")
if err != nil {
t.Fatal(err)
}

creator := &WorkspaceCreator{dir: testTempDir, client: client}
opts := ExecutorOpts{
Cache: &ExecutionNoOpCache{},
Creator: creator,
TempDir: testTempDir,
Parallelism: runtime.GOMAXPROCS(0),
Timeout: 5 * time.Second,
}

called := false
updateFn := func(task *Task, ts TaskStatus) { called = true }

executor := newExecutor(opts, client, updateFn)

template := &ChangesetTemplate{}
executor.AddTask(repo, steps, template)

executor.Start(context.Background())
specs, err := executor.Wait()
if err != nil {
t.Fatal(err)
}

if !called {
t.Fatalf("update was not called")
}

if have, want := len(specs), 1; have != want {
t.Fatalf("wrong number of specs. want=%d, have=%d", want, have)
}

if have, want := len(specs[0].Commits), 1; have != want {
t.Fatalf("wrong number of commits. want=%d, have=%d", want, have)
}

fileDiffs, err := diff.ParseMultiFileDiff([]byte(specs[0].Commits[0].Diff))
if err != nil {
t.Fatalf("failed to parse diff: %s", err)
}

diffsByName := map[string]*diff.FileDiff{}
for _, fd := range fileDiffs {
diffsByName[fd.OrigName] = fd
}

if have, want := len(diffsByName), 2; have != want {
t.Fatalf("wrong number of diffsByName. want=%d, have=%d", want, have)
}

if _, ok := diffsByName["main.go"]; !ok {
t.Errorf("main.go was not changed")
}
if _, ok := diffsByName["README.md"]; !ok {
t.Errorf("README.md was not changed")
}
}

func addToPath(t *testing.T, relPath string) {
t.Helper()

dummyDockerPath, err := filepath.Abs("testdata/dummydocker")
if err != nil {
t.Fatal(err)
}
os.Setenv("PATH", fmt.Sprintf("%s%c%s", dummyDockerPath, os.PathListSeparator, os.Getenv("PATH")))
}

func newZipHandler(t *testing.T, repo, branch string, files map[string]string) http.HandlerFunc {
wantPath := fmt.Sprintf("/%s@%s/-/raw", repo, branch)

downloadName := filepath.Base(repo)
mediaType := mime.FormatMediaType("Attachment", map[string]string{
"filename": downloadName,
})

return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != wantPath {
t.Errorf("request has wrong path. want=%q, have=%q", wantPath, r.URL.Path)
w.WriteHeader(http.StatusNotFound)
return
}

w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("Content-Type", "application/zip")
w.Header().Set("Content-Disposition", mediaType)

zipWriter := zip.NewWriter(w)

for name, body := range files {
f, err := zipWriter.Create(name)
if err != nil {
log.Fatal(err)
}
if _, err := f.Write([]byte(body)); err != nil {
t.Errorf("failed to write body for %s to zip: %s", name, err)
}
}

if err := zipWriter.Close(); err != nil {
t.Fatalf("closing zipWriter failed: %s", err)
}
}
}
10 changes: 6 additions & 4 deletions internal/campaigns/graphql/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@ fragment repositoryFields on Repository {
}
`

type Branch struct {
Name string
Target struct{ OID string }
}

type Repository struct {
ID string
Name string
URL string
ExternalRepository struct{ ServiceType string }
DefaultBranch *struct {
Name string
Target struct{ OID string }
}
DefaultBranch *Branch
}

func (r *Repository) BaseRef() string {
Expand Down
2 changes: 1 addition & 1 deletion internal/campaigns/run_steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ func runSteps(ctx context.Context, wc *WorkspaceCreator, repo *graphql.Repositor
Stderr: strings.TrimSpace(stderrBuffer.String()),
}
}
logger.Logf("[Step %d] complete in %s", i+1, elapsed)

logger.Logf("[Step %d] complete in %s", i+1, elapsed)
}

if _, err := runGitCmd("add", "--all"); err != nil {
Expand Down
52 changes: 52 additions & 0 deletions internal/campaigns/testdata/dummydocker/docker
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/bin/bash

# This script is used by the executor integration test to simulate Docker.
# It gets put into $PATH as "docker" and accepts the "run" command.

# Depending on the arguments to the "run" command it either acts like it
# created a tempfile, or it executes the script supplied as the last arg to
# "run".

dummy_temp_file="DUMMYDOCKER-TEMP-FILE"

if [[ "${1}" == "run" ]]; then
last_arg="${@: -1}"

case "$last_arg" in
"mktemp")
# If the last arg is "mktemp" we're probing for a shell image and
# want to create a temp file in the container which we can put a script.
echo "${dummy_temp_file}"
exit 0
;;

"${dummy_temp_file}")
# If the last arg is the temp file we "created" earlier, we now want to
# execute it.
#
# But the real script is in the matching host temp file, which should
# be mounted into the temp file inside the container.
#
# We need to find it in the args and then execute it.

host_temp_file=""
for i in "$@";
do
if [[ ${i} =~ ^type=bind,source=(.*),target=${dummy_temp_file},ro$ ]]; then
host_temp_file="${BASH_REMATCH[1]}"
fi
done
[ -z "$host_temp_file" ] && echo "host temp file not found in args" && exit 1;

# Now that we have the path to the host temp file, we can execute it
exec bash ${host_temp_file}
;;
*)
echo "dummydocker doesn't know about this command: $last_arg"
exit 1
;;
esac
fi

echo "dummydocker doesn't know about this command: $1"
exit 1