Skip to content

Commit 2d8c956

Browse files
committed
Add tests
1 parent bdfb061 commit 2d8c956

File tree

11 files changed

+253
-46
lines changed

11 files changed

+253
-46
lines changed

modules/git/attribute/attribute.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,12 @@ var LinguistAttributes = []string{
2929
GitlabLanguage,
3030
}
3131

32+
func (a Attribute) IsUnspecified() bool {
33+
return a == "" || a == "unspecified"
34+
}
35+
3236
func (a Attribute) ToString() optional.Option[string] {
33-
if a != "" && a != "unspecified" {
37+
if !a.IsUnspecified() {
3438
return optional.Some(string(a))
3539
}
3640
return optional.None[string]()
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package attribute
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func Test_Attribute(t *testing.T) {
13+
assert.Empty(t, Attribute("").ToString().Value())
14+
assert.Empty(t, Attribute("unspecified").ToString().Value())
15+
assert.Equal(t, "python", Attribute("python").ToString().Value())
16+
assert.Equal(t, "Java", Attribute("Java").ToString().Value())
17+
18+
attributes := Attributes{
19+
LinguistGenerated: "true",
20+
LinguistDocumentation: "false",
21+
LinguistDetectable: "set",
22+
LinguistLanguage: "Python",
23+
GitlabLanguage: "Java",
24+
"filter": "unspecified",
25+
"test": "",
26+
}
27+
28+
assert.Empty(t, attributes.Get("test").ToString().Value())
29+
assert.Empty(t, attributes.Get("filter").ToString().Value())
30+
assert.Equal(t, "Python", attributes.Get(LinguistLanguage).ToString().Value())
31+
assert.Equal(t, "Java", attributes.Get(GitlabLanguage).ToString().Value())
32+
assert.True(t, attributes.Get(LinguistGenerated).ToBool().Value())
33+
assert.False(t, attributes.Get(LinguistDocumentation).ToBool().Value())
34+
assert.True(t, attributes.Get(LinguistDetectable).ToBool().Value())
35+
}

modules/git/attribute/batch.go

Lines changed: 23 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"bytes"
88
"context"
99
"fmt"
10-
"io"
1110
"os"
1211
"path/filepath"
1312
"time"
@@ -19,15 +18,11 @@ import (
1918
// BatchChecker provides a reader for check-attribute content that can be long running
2019
type BatchChecker struct {
2120
// params
22-
Attributes []string
23-
Repo *git.Repository
24-
Treeish string
21+
attributesNum int
22+
repo *git.Repository
2523

26-
stdinReader io.ReadCloser
2724
stdinWriter *os.File
2825
stdOut *nulSeparatedAttributeWriter
29-
cmd *git.Command
30-
env []string
3126
ctx context.Context
3227
cancel context.CancelFunc
3328
}
@@ -46,31 +41,41 @@ func NewBatchChecker(repo *git.Repository, treeish string, attributes ...string)
4641
cmd.AddArguments("--stdin")
4742

4843
checker := &BatchChecker{
49-
Attributes: attributes,
50-
Repo: repo,
51-
Treeish: treeish,
52-
ctx: ctx,
44+
attributesNum: len(attributes),
45+
repo: repo,
46+
ctx: ctx,
5347
cancel: func() {
5448
cancel()
5549
cleanup()
5650
},
57-
cmd: cmd,
58-
env: envs,
5951
}
6052

61-
checker.stdinReader, checker.stdinWriter, err = os.Pipe()
53+
stdinReader, stdinWriter, err := os.Pipe()
6254
if err != nil {
6355
checker.cancel()
6456
return nil, err
6557
}
58+
checker.stdinWriter = stdinWriter
6659

6760
lw := new(nulSeparatedAttributeWriter)
6861
lw.attributes = make(chan attributeTriple, 5)
6962
lw.closed = make(chan struct{})
7063
checker.stdOut = lw
7164

7265
go func() {
73-
err := checker.run(ctx)
66+
defer func() {
67+
_ = stdinReader.Close()
68+
_ = lw.Close()
69+
}()
70+
stdErr := new(bytes.Buffer)
71+
err := cmd.Run(ctx, &git.RunOpts{
72+
Env: envs,
73+
Dir: repo.Path,
74+
Stdin: stdinReader,
75+
Stdout: lw,
76+
Stderr: stdErr,
77+
})
78+
7479
if err != nil && !git.IsErrCanceledOrKilled(err) {
7580
log.Error("Attribute checker for commit %s exits with error: %v", treeish, err)
7681
}
@@ -80,30 +85,11 @@ func NewBatchChecker(repo *git.Repository, treeish string, attributes ...string)
8085
return checker, nil
8186
}
8287

83-
func (c *BatchChecker) run(ctx context.Context) error {
84-
defer func() {
85-
_ = c.stdinReader.Close()
86-
_ = c.stdOut.Close()
87-
}()
88-
stdErr := new(bytes.Buffer)
89-
err := c.cmd.Run(ctx, &git.RunOpts{
90-
Env: c.env,
91-
Dir: c.Repo.Path,
92-
Stdin: c.stdinReader,
93-
Stdout: c.stdOut,
94-
Stderr: stdErr,
95-
})
96-
if err != nil && !git.IsErrCanceledOrKilled(err) {
97-
return fmt.Errorf("failed to run attr-check. Error: %w\nStderr: %s", err, stdErr.String())
98-
}
99-
return nil
100-
}
101-
10288
// CheckPath check attr for given path
10389
func (c *BatchChecker) CheckPath(path string) (rs Attributes, err error) {
10490
defer func() {
10591
if err != nil && err != c.ctx.Err() {
106-
log.Error("Unexpected error when checking path %s in %s, error: %v", path, filepath.Base(c.Repo.Path), err)
92+
log.Error("Unexpected error when checking path %s in %s, error: %v", path, filepath.Base(c.repo.Path), err)
10793
}
10894
}()
10995

@@ -125,7 +111,7 @@ func (c *BatchChecker) CheckPath(path string) (rs Attributes, err error) {
125111
stdOutClosed = true
126112
default:
127113
}
128-
debugMsg := fmt.Sprintf("check path %q in repo %q", path, filepath.Base(c.Repo.Path))
114+
debugMsg := fmt.Sprintf("check path %q in repo %q", path, filepath.Base(c.repo.Path))
129115
debugMsg += fmt.Sprintf(", stdOut: tmp=%q, pos=%d, closed=%v", string(c.stdOut.tmp), c.stdOut.pos, stdOutClosed)
130116
// FIXME:
131117
//if c.cmd.cmd != nil {
@@ -136,7 +122,7 @@ func (c *BatchChecker) CheckPath(path string) (rs Attributes, err error) {
136122
}
137123

138124
rs = make(map[string]Attribute)
139-
for range c.Attributes {
125+
for i := 0; i < c.attributesNum; i++ {
140126
select {
141127
case <-time.After(5 * time.Second):
142128
// There is a strange "hang" problem in gitdiff.GetDiff -> CheckPath

modules/git/attribute/batch_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@
44
package attribute
55

66
import (
7+
"path/filepath"
78
"testing"
89
"time"
910

11+
"code.gitea.io/gitea/modules/git"
12+
"code.gitea.io/gitea/modules/setting"
13+
"code.gitea.io/gitea/modules/test"
1014
"github.com/stretchr/testify/assert"
15+
"github.com/stretchr/testify/require"
1116
)
1217

1318
func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
@@ -95,3 +100,68 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
95100
Value: "unspecified",
96101
}, attr)
97102
}
103+
104+
var expectedAttrs = Attributes{
105+
LinguistGenerated: "unspecified",
106+
LinguistDetectable: "unspecified",
107+
LinguistDocumentation: "unspecified",
108+
LinguistVendored: "unspecified",
109+
LinguistLanguage: "Python",
110+
GitlabLanguage: "unspecified",
111+
}
112+
113+
func Test_BatchChecker(t *testing.T) {
114+
setting.AppDataPath = t.TempDir()
115+
repoPath := "../tests/repos/language_stats_repo"
116+
gitRepo, err := git.OpenRepository(t.Context(), repoPath)
117+
require.NoError(t, err)
118+
defer gitRepo.Close()
119+
120+
commitID := "8fee858da5796dfb37704761701bb8e800ad9ef3"
121+
122+
t.Run("Create index file to run git check-attr", func(t *testing.T) {
123+
defer test.MockVariableValue(&git.DefaultFeatures().SupportCheckAttrOnBare, false)()
124+
checker, err := NewBatchChecker(gitRepo, commitID, LinguistAttributes...)
125+
assert.NoError(t, err)
126+
defer checker.Close()
127+
attributes, err := checker.CheckPath("i-am-a-python.p")
128+
assert.NoError(t, err)
129+
assert.Equal(t, expectedAttrs, attributes)
130+
})
131+
132+
// run git check-attr on work tree
133+
t.Run("Run git check-attr on git work tree", func(t *testing.T) {
134+
dir := filepath.Join(t.TempDir(), "test-repo")
135+
err := git.Clone(t.Context(), repoPath, dir, git.CloneRepoOptions{
136+
Shared: true,
137+
Branch: "master",
138+
})
139+
assert.NoError(t, err)
140+
141+
tempRepo, err := git.OpenRepository(t.Context(), dir)
142+
assert.NoError(t, err)
143+
defer tempRepo.Close()
144+
145+
checker, err := NewBatchChecker(tempRepo, "", LinguistAttributes...)
146+
assert.NoError(t, err)
147+
defer checker.Close()
148+
attributes, err := checker.CheckPath("i-am-a-python.p")
149+
assert.NoError(t, err)
150+
assert.Equal(t, expectedAttrs, attributes)
151+
})
152+
153+
if !git.DefaultFeatures().SupportCheckAttrOnBare {
154+
t.Skip("git version 2.40 is required to support run check-attr on bare repo")
155+
return
156+
}
157+
158+
t.Run("Run git check-attr in bare repository", func(t *testing.T) {
159+
checker, err := NewBatchChecker(gitRepo, commitID, LinguistAttributes...)
160+
assert.NoError(t, err)
161+
defer checker.Close()
162+
163+
attributes, err := checker.CheckPath("i-am-a-python.p")
164+
assert.NoError(t, err)
165+
assert.Equal(t, expectedAttrs, attributes)
166+
})
167+
}

modules/git/attribute/checker.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ type CheckAttributeOpts struct {
6767
Attributes []string
6868
}
6969

70-
// CheckAttribute return the Blame object of file
71-
func CheckAttribute(ctx context.Context, gitRepo *git.Repository, treeish string, opts CheckAttributeOpts) (map[string]Attributes, error) {
70+
// CheckAttributes return the attributes of the given filenames and attributes in the given treeish.
71+
func CheckAttributes(ctx context.Context, gitRepo *git.Repository, treeish string, opts CheckAttributeOpts) (map[string]Attributes, error) {
7272
cmd, envs, cancel, err := checkAttrCommand(gitRepo, treeish, opts.Filenames, opts.Attributes)
7373
if err != nil {
7474
return nil, err
@@ -88,13 +88,11 @@ func CheckAttribute(ctx context.Context, gitRepo *git.Repository, treeish string
8888
}
8989

9090
fields := bytes.Split(stdOut.Bytes(), []byte{'\000'})
91-
9291
if len(fields)%3 != 1 {
9392
return nil, errors.New("wrong number of fields in return from check-attr")
9493
}
9594

9695
attributesMap := make(map[string]Attributes)
97-
9896
for i := 0; i < (len(fields) / 3); i++ {
9997
filename := string(fields[3*i])
10098
attribute := string(fields[3*i+1])

modules/git/attribute/checker_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package attribute
5+
6+
import (
7+
"path/filepath"
8+
"testing"
9+
10+
"code.gitea.io/gitea/modules/git"
11+
"code.gitea.io/gitea/modules/setting"
12+
"code.gitea.io/gitea/modules/test"
13+
14+
"github.com/stretchr/testify/assert"
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
func Test_Checker(t *testing.T) {
19+
setting.AppDataPath = t.TempDir()
20+
repoPath := "../tests/repos/language_stats_repo"
21+
gitRepo, err := git.OpenRepository(t.Context(), repoPath)
22+
require.NoError(t, err)
23+
defer gitRepo.Close()
24+
25+
commitID := "8fee858da5796dfb37704761701bb8e800ad9ef3"
26+
27+
t.Run("Create index file to run git check-attr", func(t *testing.T) {
28+
defer test.MockVariableValue(&git.DefaultFeatures().SupportCheckAttrOnBare, false)()
29+
attrs, err := CheckAttributes(t.Context(), gitRepo, commitID, CheckAttributeOpts{
30+
Filenames: []string{"i-am-a-python.p"},
31+
Attributes: LinguistAttributes,
32+
})
33+
assert.NoError(t, err)
34+
assert.Len(t, attrs, 1)
35+
assert.Equal(t, expectedAttrs, attrs["i-am-a-python.p"])
36+
})
37+
38+
// run git check-attr on work tree
39+
t.Run("Run git check-attr on git work tree", func(t *testing.T) {
40+
dir := filepath.Join(t.TempDir(), "test-repo")
41+
err := git.Clone(t.Context(), repoPath, dir, git.CloneRepoOptions{
42+
Shared: true,
43+
Branch: "master",
44+
})
45+
assert.NoError(t, err)
46+
47+
tempRepo, err := git.OpenRepository(t.Context(), dir)
48+
assert.NoError(t, err)
49+
defer tempRepo.Close()
50+
51+
attrs, err := CheckAttributes(t.Context(), tempRepo, "", CheckAttributeOpts{
52+
Filenames: []string{"i-am-a-python.p"},
53+
Attributes: LinguistAttributes,
54+
})
55+
assert.NoError(t, err)
56+
assert.Len(t, attrs, 1)
57+
assert.Equal(t, expectedAttrs, attrs["i-am-a-python.p"])
58+
})
59+
60+
if !git.DefaultFeatures().SupportCheckAttrOnBare {
61+
t.Skip("git version 2.40 is required to support run check-attr on bare repo")
62+
return
63+
}
64+
65+
t.Run("Run git check-attr in bare repository", func(t *testing.T) {
66+
attrs, err := CheckAttributes(t.Context(), gitRepo, commitID, CheckAttributeOpts{
67+
Filenames: []string{"i-am-a-python.p"},
68+
Attributes: LinguistAttributes,
69+
})
70+
assert.NoError(t, err)
71+
assert.Len(t, attrs, 1)
72+
assert.Equal(t, expectedAttrs, attrs["i-am-a-python.p"])
73+
})
74+
}

0 commit comments

Comments
 (0)