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}