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}