GitRoot

craft your forge, build your project, grow your community freely
  1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
  2//
  3// SPDX-License-Identifier: EUPL-1.2
  4
  5package exec
  6
  7import (
  8	"context"
  9	_ "embed"
 10	"fmt"
 11	"os"
 12	"path/filepath"
 13	"strconv"
 14	"strings"
 15	"testing"
 16
 17	"github.com/go-git/go-git/v5/plumbing"
 18	"gitroot.dev/libs/golang/plugin/model"
 19	"gitroot.dev/server/configuration"
 20	"gitroot.dev/server/repository"
 21	"gitroot.dev/server/user"
 22)
 23
 24const pluginCacheDir = "pluginCache"
 25
 26//go:embed resources/GitRoot.priv
 27var gitrootPriv []byte
 28
 29//go:embed resources/GitRoot.pub
 30var gitrootPub []byte
 31
 32type fakeConf struct {
 33	kind    string
 34	dataDir string
 35}
 36
 37func (f *fakeConf) GetExecConf() configuration.ExecConf {
 38	dc := configuration.ExecDefaultConf
 39	switch f.kind {
 40	case "bareMetal":
 41		dc.BareMetal.Enabled = true
 42	case "bwrap":
 43		dc.Bwrap.Enabled = true
 44	case "container":
 45		dc.Container.Enabled = true
 46		dc.Container.Bin = "podman"
 47	case "ssh":
 48		dc.Ssh.Enabled = true
 49		u := os.Getenv("SSH_USER")
 50		a := os.Getenv("SSH_ADDR")
 51		p := os.Getenv("SSH_PORT")
 52		port, _ := strconv.Atoi(p)
 53		dc.Ssh.Hosts = []configuration.Host{
 54			{User: u, Address: a, Port: port, PublicKey: "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCBMx22obajd8AZ75MtevWOR62MHAI3Lf2pMt5rDjgdPJUy2v3ncdSdod8qbsJ7Ie0I3OMlq+21Lr51PLNJj5Ok="},
 55		}
 56	}
 57	return dc
 58}
 59
 60func (f *fakeConf) PathCacheProject(repoName string, pluginName string) string {
 61	return filepath.Join(f.dataDir, pluginCacheDir)
 62}
 63
 64type testDate struct {
 65	executor string
 66	build    string
 67	status   *model.ExecStatus
 68	env      []string
 69}
 70
 71var testData = []*testDate{
 72	{executor: "bareMetal", env: []string{"MISE_CACHE_DIR=/cache/mise"}},
 73	{executor: "bwrap", env: []string{"MISE_CACHE_DIR=/cache/mise"}},
 74	{executor: "container", build: "docker.io/bash:5.3.3", env: []string{"MISE_CACHE_DIR=/cache/mise"}},
 75	{executor: "container", build: "./Containerfile", env: []string{"MISE_CACHE_DIR=/cache/mise"}},
 76	// WARN if you uncomment that be sure to set env look line 47 of this file
 77	//{executor: "ssh", env: []string{"MISE_CACHE_DIR=/cache/mise"}},
 78}
 79
 80func TestExec(t *testing.T) {
 81	for _, tt := range testData {
 82		dataDir := t.TempDir()
 83		conf := configuration.NewConfiguration(dataDir)
 84		userManager, _ := user.NewManager(conf)
 85		os.WriteFile(filepath.Join(conf.GetDirPathData(), fmt.Sprintf("%s.pub", conf.RootCommiterPseudo())), gitrootPub, os.ModeExclusive)
 86		os.WriteFile(filepath.Join(conf.GetDirPathData(), fmt.Sprintf("%s.priv", conf.RootCommiterPseudo())), gitrootPriv, os.ModeExclusive)
 87		userManager, _ = user.NewManager(conf)
 88		m := NewManager(t.Context(), &fakeConf{kind: tt.executor, dataDir: dataDir}, userManager)
 89		repoManager := repository.NewManager(conf, userManager)
 90		err := repoManager.CreateUserRepo(context.TODO(), repository.RepoConf{Name: "exec", DefaultBranch: "main"}, []user.SimpleUser{}, []byte(""))
 91		if err != nil {
 92			t.Fatal(err)
 93		}
 94		repo, err := repoManager.Open(t.Context(), "exec")
 95		if err != nil {
 96			t.Fatal(err)
 97		}
 98		repoWrite, _ := repo.WillWrite(plumbing.NewBranchReferenceName("main"))
 99		repoWrite.Write("Containerfile", []byte("from docker.io/bash:5.3.3"))
100		repoWrite.CommitAll("add containerfile", userManager.RootCommiter())
101		repoWrite.Accept()
102		repoWrite.Storer().Commit()
103
104		cmds := []model.Cmd{
105			{Cmd: "echo", Args: []string{"$MISE_CACHE_DIR"}},
106			{Cmd: "echo", Args: []string{"$MISE_CACHE_DIR > arti"}},
107			{Cmd: "ls", Args: []string{"-a", "."}},
108			{Cmd: "dd", Args: []string{"if=/dev/zero", "of=test_write.tmp", "bs=1M", "count=500", "conv=fdatasync", "oflag=dsync"}},
109			{Cmd: "rm", Args: []string{"test_write.tmp"}},
110		}
111
112		if tt.executor == "bareMetal" {
113			cmds = append(cmds, model.Cmd{Cmd: "echo", Args: []string{"Hello world > $PWD$MISE_CACHE_DIR/myFileCache"}})
114		} else {
115			cmds = append(cmds, model.Cmd{Cmd: "echo", Args: []string{"Hello world > $MISE_CACHE_DIR/myFileCache"}})
116		}
117
118		status, err := m.Exec(repo, "main", "pluginName", model.Exec{
119			Build:       tt.build,
120			ReportStats: true,
121			Cmds:        cmds,
122			Env:         tt.env,
123			Artifacts:   []string{"arti"},
124			Cache:       []model.Cache{{Key: "keyCacheDir", Path: "/cache/mise", ReadOnly: false}},
125		})
126		if err != nil {
127			t.Fatal(err)
128		}
129
130		tt.status = status
131
132		if len(tt.status.CmdsLogs) < 6 {
133			t.Fatal("All step should have logs")
134		}
135		hasContainer := tt.executor != "bareMetal" && tt.executor != "ssh"
136		firstIndex := 0
137		if hasContainer {
138			firstIndex++
139		}
140		hasBuild := strings.HasPrefix(tt.build, "./")
141		if hasBuild {
142			firstIndex++
143		}
144		contentLog, err := os.ReadFile(filepath.Join(dataDir, pluginCacheDir, tt.status.CmdsLogs[firstIndex]))
145		if err != nil {
146			t.Fatal(err)
147		}
148		if !strings.Contains(string(contentLog), "/cache/mise") {
149			t.Fatal(string(contentLog))
150		}
151
152		contentLog, err = os.ReadFile(filepath.Join(dataDir, pluginCacheDir, tt.status.CmdsLogs[firstIndex+2]))
153		if err != nil {
154			t.Fatal(err)
155		}
156		if !strings.Contains(string(contentLog), ".gitroot") {
157			t.Fatal(string(contentLog))
158		}
159
160		if len(tt.status.Artifacts) < 1 {
161			t.Fatal("should have artifacts")
162		}
163		contentArtifact, err := os.ReadFile(filepath.Join(dataDir, pluginCacheDir, tt.status.Artifacts[0]))
164		if err != nil {
165			t.Fatal(err)
166		}
167		if !strings.Contains(string(contentArtifact), "/cache/mise") {
168			t.Fatal(contentArtifact)
169		}
170
171		contentCache, err := os.ReadFile(filepath.Join(dataDir, pluginCacheDir, "cache", "keyCacheDir", "myFileCache"))
172		if err != nil {
173			t.Fatal(err)
174		}
175		if !strings.Contains(string(contentCache), "Hello world") {
176			t.Fatal(contentCache)
177		}
178	}
179
180	t.Log("|executor|  cmd |status|memory|cpu|nb thread|read|write|")
181	t.Log("|--------|------|------|------|---|---------|----|-----|")
182	for _, tt := range testData {
183		for i, stat := range tt.status.CmdsStats {
184			t.Logf("|%s|%s|%d|%d|%d|%d|%d|%d|", tt.executor, tt.status.CmdsExec[i], tt.status.CmdsStatus[i], stat.MaxMemoryBytes, stat.TotalCPUTimeMs, stat.MaxThreads, stat.ReadBytesTotal, stat.WriteBytesTotal)
185		}
186	}
187
188	//uncomment to see stats
189	t.FailNow()
190}