arnaucube
arnaucube/gitviewPublic Self-hosted repository
gitview/internal/git/languages_test.go
internal/git/languages_test.goRaw
  1package git
  2
  3import (
  4	"math"
  5	"os"
  6	"path/filepath"
  7	"testing"
  8	"time"
  9
 10	gogit "github.com/go-git/go-git/v5"
 11	"github.com/go-git/go-git/v5/plumbing/object"
 12)
 13
 14// commitLanguageFiles creates real Git objects, including bare-mirror compatible trees.
 15func commitLanguageFiles(t *testing.T, files map[string]string) (string, *gogit.Repository) {
 16	t.Helper()
 17	path := t.TempDir()
 18	repo, err := gogit.PlainInit(path, false)
 19	if err != nil {
 20		t.Fatal(err)
 21	}
 22	wt, err := repo.Worktree()
 23	if err != nil {
 24		t.Fatal(err)
 25	}
 26	for name, content := range files {
 27		target := filepath.Join(path, name)
 28		if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
 29			t.Fatal(err)
 30		}
 31		if err := os.WriteFile(target, []byte(content), 0644); err != nil {
 32			t.Fatal(err)
 33		}
 34		if _, err := wt.Add(name); err != nil {
 35			t.Fatal(err)
 36		}
 37	}
 38	author := &object.Signature{Name: "Test Author", Email: "author@example.com", When: time.Now()}
 39	if _, err := wt.Commit("Language fixtures", &gogit.CommitOptions{Author: author}); err != nil {
 40		t.Fatal(err)
 41	}
 42	return path, repo
 43}
 44
 45func TestLanguagesSourceBytesAndExclusions(t *testing.T) {
 46	files := map[string]string{
 47		"main.go":                   "package main\n\nfunc main() {}\n",
 48		"lib/helpers.go":            "package main\n\nconst answer = 42\n",
 49		"src/index.js":              "export const answer = 42;\n",
 50		"page.html":                 "<!doctype html><html><body>Hello</body></html>\n",
 51		"README.md":                 "# Documentation\n",
 52		"data.json":                 "{\"name\":\"data\"}",
 53		"settings.yaml":             "enabled: true\n",
 54		"binary.go":                 "\x00\x01\x02not source",
 55		"empty.py":                  "",
 56		"vendor/dep/code.go":        "package dependency\n",
 57		"node_modules/pkg/index.js": "export const dependency = true;\n",
 58		"generated.go":              "// Code generated by a tool. DO NOT EDIT.\npackage main\n",
 59	}
 60	path, _ := commitLanguageFiles(t, files)
 61	if err := os.Symlink("main.go", filepath.Join(path, "alias.py")); err != nil {
 62		t.Fatal(err)
 63	}
 64	repo, err := gogit.PlainOpen(path)
 65	if err != nil {
 66		t.Fatal(err)
 67	}
 68	wt, err := repo.Worktree()
 69	if err != nil {
 70		t.Fatal(err)
 71	}
 72	if _, err := wt.Add("alias.py"); err != nil {
 73		t.Fatal(err)
 74	}
 75	if _, err := wt.Commit("Add source link", &gogit.CommitOptions{Author: &object.Signature{Name: "Test", Email: "test@example.com", When: time.Now()}}); err != nil {
 76		t.Fatal(err)
 77	}
 78	g, err := Open(path, "")
 79	if err != nil {
 80		t.Fatal(err)
 81	}
 82	stats, err := g.Languages()
 83	if err != nil {
 84		t.Fatal(err)
 85	}
 86	want := map[string]int64{"Go": int64(len(files["main.go"]) + len(files["lib/helpers.go"])), "JavaScript": int64(len(files["src/index.js"])), "HTML": int64(len(files["page.html"]))}
 87	if len(stats) != len(want) {
 88		t.Fatalf("languages: %+v; want %v", stats, want)
 89	}
 90	var total int64
 91	for _, size := range want {
 92		total += size
 93	}
 94	for i, stat := range stats {
 95		if stat.Bytes != want[stat.Name] {
 96			t.Errorf("%s: %d bytes, want %d", stat.Name, stat.Bytes, want[stat.Name])
 97		}
 98		percentage := 100 * float64(want[stat.Name]) / float64(total)
 99		if math.Abs(stat.Percentage-percentage) > 1e-8 {
100			t.Errorf("%s: percentage %f, want %f", stat.Name, stat.Percentage, percentage)
101		}
102		if i > 0 && stat.Bytes > stats[i-1].Bytes {
103			t.Error("languages are not sorted by code size")
104		}
105	}
106	// Mirrors have no working tree but must produce the same statistics.
107	barePath := filepath.Join(t.TempDir(), "mirror.git")
108	if _, err := gogit.PlainClone(barePath, true, &gogit.CloneOptions{URL: path}); err != nil {
109		t.Fatal(err)
110	}
111	bare, err := Open(barePath, "")
112	if err != nil {
113		t.Fatal(err)
114	}
115	bareStats, err := bare.Languages()
116	if err != nil {
117		t.Fatal(err)
118	}
119	if len(bareStats) != len(stats) {
120		t.Fatalf("bare mirror statistics: %+v", bareStats)
121	}
122}
123
124func TestLanguagesCacheFollowsCommits(t *testing.T) {
125	path, repo := commitLanguageFiles(t, map[string]string{"main.go": "package main\n"})
126	g, err := Open(path, "")
127	if err != nil {
128		t.Fatal(err)
129	}
130	before, err := g.Languages()
131	if err != nil {
132		t.Fatal(err)
133	}
134	if len(before) != 1 || before[0].Name != "Go" {
135		t.Fatalf("initial languages: %+v", before)
136	}
137	before[0].Name = "mutated"
138	cached, err := g.Languages()
139	if err != nil {
140		t.Fatal(err)
141	}
142	if cached[0].Name != "Go" {
143		t.Fatal("caller modified the cached statistics")
144	}
145	if err := os.WriteFile(filepath.Join(path, "main.py"), []byte("print('new language')\n"), 0644); err != nil {
146		t.Fatal(err)
147	}
148	wt, err := repo.Worktree()
149	if err != nil {
150		t.Fatal(err)
151	}
152	if _, err := wt.Add("main.py"); err != nil {
153		t.Fatal(err)
154	}
155	if _, err := wt.Commit("Add Python", &gogit.CommitOptions{Author: &object.Signature{Name: "Test", Email: "test@example.com", When: time.Now()}}); err != nil {
156		t.Fatal(err)
157	}
158	updated, err := Open(path, "")
159	if err != nil {
160		t.Fatal(err)
161	}
162	after, err := updated.Languages()
163	if err != nil {
164		t.Fatal(err)
165	}
166	if len(after) != 2 || after[0].Name != "Python" {
167		t.Fatalf("new commit did not refresh statistics: %+v", after)
168	}
169}
170
171func TestLanguagesDocumentationOnly(t *testing.T) {
172	path, _ := commitLanguageFiles(t, map[string]string{"README.md": "# Just documentation\n"})
173	g, err := Open(path, "")
174	if err != nil {
175		t.Fatal(err)
176	}
177	stats, err := g.Languages()
178	if err != nil {
179		t.Fatal(err)
180	}
181	if len(stats) != 0 {
182		t.Fatalf("documentation counted as source: %+v", stats)
183	}
184}