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