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	"os"
 11	"path/filepath"
 12	"strconv"
 13	"strings"
 14	"testing"
 15
 16	"github.com/go-git/go-git/v5/plumbing"
 17	"gitroot.dev/libs/golang/plugin/model"
 18	"gitroot.dev/server/configuration"
 19	"gitroot.dev/server/repository"
 20	"gitroot.dev/server/user"
 21)
 22
 23const pluginCacheDir = "pluginCache"
 24
 25///go:embed resources/GitRoot.priv
 26// var gitrootPriv []byte
 27
 28///go:embed resources/GitRoot.pub
 29// var gitrootPub []byte
 30
 31type fakeConf struct {
 32	kind    string
 33	dataDir string
 34}
 35
 36func (f *fakeConf) GetExecConf() configuration.ExecConf {
 37	dc := configuration.ExecDefaultConf
 38	switch f.kind {
 39	case "bareMetal":
 40		dc.BareMetal.Enabled = true
 41	case "bwrap":
 42		dc.Bwrap.Enabled = true
 43	case "container":
 44		dc.Container.Enabled = true
 45		dc.Container.Bin = "podman"
 46	case "ssh":
 47		dc.Ssh.Enabled = true
 48		u := os.Getenv("SSH_USER")
 49		a := os.Getenv("SSH_ADDR")
 50		p := os.Getenv("SSH_PORT")
 51		port, _ := strconv.Atoi(p)
 52		dc.Ssh.Hosts = []configuration.Host{
 53			{User: u, Address: a, Port: port, PublicKey: "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCBMx22obajd8AZ75MtevWOR62MHAI3Lf2pMt5rDjgdPJUy2v3ncdSdod8qbsJ7Ie0I3OMlq+21Lr51PLNJj5Ok="},
 54		}
 55	}
 56	return dc
 57}
 58
 59func (f *fakeConf) PathCacheProject(repoName string, pluginName string) string {
 60	return filepath.Join(f.dataDir, pluginCacheDir)
 61}
 62
 63type testDate struct {
 64	executor string
 65	build    string
 66	status   *model.ExecStatus
 67	env      []string
 68}
 69
 70var testData = []*testDate{
 71	{executor: "bareMetal", env: []string{"MISE_CACHE_DIR=/cache/mise"}},
 72	{executor: "bwrap", env: []string{"MISE_CACHE_DIR=/cache/mise"}},
 73	//TODO no container on gitroot.dev instance
 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}