Move integration tests to tests/ directory (#973)

Reviewed-on: https://gitea.com/gitea/tea/pulls/973
This commit is contained in:
Lunny Xiao
2026-05-02 04:18:45 +00:00
parent 1f6fd97fc1
commit 83b718ac34
9 changed files with 184 additions and 101 deletions

View File

@@ -12,13 +12,9 @@ jobs:
# uses: golang/govulncheck-action@v1 # uses: golang/govulncheck-action@v1
# with: # with:
# go-version-file: 'go.mod' # go-version-file: 'go.mod'
check-and-test: check-and-unit:
name: Lint Build And Unit Coverage
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
HTTP_PROXY: ""
GITEA_TEA_TEST_URL: "http://gitea:3000"
GITEA_TEA_TEST_USERNAME: "test01"
GITEA_TEA_TEST_PASSWORD: "test01"
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
@@ -32,11 +28,27 @@ jobs:
make fmt-check make fmt-check
make docs-check make docs-check
make build make build
- run: curl --noproxy "*" http://gitea:3000/api/v1/version # verify connection to instance - name: unit test and coverage
- name: test and coverage
run: | run: |
make test
make unit-test-coverage make unit-test-coverage
integration-test:
name: Integration Test
runs-on: ubuntu-latest
env:
HTTP_PROXY: ""
GITEA_TEA_TEST_URL: "http://gitea:3000"
GITEA_TEA_TEST_USERNAME: "test01"
GITEA_TEA_TEST_PASSWORD: "test01"
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- run: curl --noproxy "*" http://gitea:3000/api/v1/version # verify connection to instance
- name: integration test
run: |
make integration-test
services: services:
gitea: gitea:
image: docker.gitea.com/gitea:1.26.1 image: docker.gitea.com/gitea:1.26.1

View File

@@ -30,7 +30,10 @@ LDFLAGS := -X "code.gitea.io/tea/modules/version.Version=$(TEA_VERSION)" -X "cod
# override to allow passing additional goflags via make CLI # override to allow passing additional goflags via make CLI
override GOFLAGS := $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' override GOFLAGS := $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)'
PACKAGES ?= $(shell $(GO) list ./...) PACKAGES ?= $(shell $(GO) list ./... | grep -v '^code.gitea.io/tea/tests')
UNIT_PACKAGES ?= $(PACKAGES)
INTEGRATION_PACKAGES ?= $(shell $(GO) list ./tests/... 2>/dev/null)
INTEGRATION_TEST_TAGS ?= testtools
SOURCES ?= $(shell find . -name "*.go" -type f) SOURCES ?= $(shell find . -name "*.go" -type f)
# OS specific vars. # OS specific vars.
@@ -64,11 +67,11 @@ vet:
.PHONY: lint .PHONY: lint
lint: lint:
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GO) run $(GOLANGCI_LINT_PACKAGE) run --build-tags testtools
.PHONY: lint-fix .PHONY: lint-fix
lint-fix: lint-fix:
$(GO) run $(GOLANGCI_LINT_PACKAGE) run --fix $(GO) run $(GOLANGCI_LINT_PACKAGE) run --build-tags testtools --fix
.PHONY: fmt-check .PHONY: fmt-check
fmt-check: fmt-check:
@@ -93,13 +96,24 @@ docs-check:
exit 1; \ exit 1; \
fi; fi;
.PHONY: unit-test
unit-test:
$(GO) test $(UNIT_PACKAGES)
.PHONY: integration-test
integration-test:
@if [ -n "$(INTEGRATION_PACKAGES)" ]; then \
$(GO) test -tags='$(INTEGRATION_TEST_TAGS)' $(INTEGRATION_PACKAGES); \
else \
echo "No integration test packages found"; \
fi
.PHONY: test .PHONY: test
test: test: unit-test integration-test
$(GO) test -tags='sqlite sqlite_unlock_notify' $(PACKAGES)
.PHONY: unit-test-coverage .PHONY: unit-test-coverage
unit-test-coverage: unit-test-coverage:
$(GO) test -tags='sqlite sqlite_unlock_notify' -cover -coverprofile coverage.out $(PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1 $(GO) test -cover -coverprofile coverage.out $(UNIT_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
.PHONY: tidy .PHONY: tidy
tidy: tidy:
@@ -122,4 +136,3 @@ $(EXECUTABLE): $(SOURCES)
.PHONY: build-image .PHONY: build-image
build-image: build-image:
docker build --build-arg VERSION=$(TEA_VERSION) -t gitea/tea:$(TEA_VERSION_TAG) . docker build --build-arg VERSION=$(TEA_VERSION) -t gitea/tea:$(TEA_VERSION_TAG) .

View File

@@ -0,0 +1,13 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build testtools
package config
import "time"
// AcquireConfigLockForTesting exposes the internal lock helper to integration tests.
func AcquireConfigLockForTesting(lockPath string, timeout time.Duration) (func() error, error) {
return acquireConfigLock(lockPath, timeout)
}

View File

@@ -4,14 +4,9 @@
package context package context
import ( import (
"os"
"os/exec"
"testing" "testing"
"code.gitea.io/tea/modules/config" "code.gitea.io/tea/modules/config"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v3"
) )
func Test_MatchLogins(t *testing.T) { func Test_MatchLogins(t *testing.T) {
@@ -70,47 +65,3 @@ func Test_MatchLogins(t *testing.T) {
}) })
} }
} }
func TestInitCommand_WithRepoSlugSkipsLocalRepoDetection(t *testing.T) {
tmpDir := t.TempDir()
config.SetConfigForTesting(config.LocalConfig{
Logins: []config.Login{{
Name: "test-login",
URL: "https://gitea.example.com",
Token: "token",
User: "login-user",
Default: true,
}},
})
cmd := exec.Command("git", "init", "--object-format=sha256", tmpDir)
cmd.Env = os.Environ()
require.NoError(t, cmd.Run())
oldWd, err := os.Getwd()
require.NoError(t, err)
require.NoError(t, os.Chdir(tmpDir))
t.Cleanup(func() {
require.NoError(t, os.Chdir(oldWd))
})
cliCmd := cli.Command{
Name: "branches",
Flags: []cli.Flag{
&cli.StringFlag{Name: "login"},
&cli.StringFlag{Name: "repo"},
&cli.StringFlag{Name: "remote"},
&cli.StringFlag{Name: "output"},
},
}
require.NoError(t, cliCmd.Set("repo", "owner/repo"))
ctx, err := InitCommand(&cliCmd)
require.NoError(t, err)
require.Equal(t, "owner", ctx.Owner)
require.Equal(t, "repo", ctx.Repo)
require.Equal(t, "owner/repo", ctx.RepoSlug)
require.Nil(t, ctx.LocalRepo)
require.NotNil(t, ctx.Login)
require.Equal(t, "test-login", ctx.Login.Name)
}

10
tests/README.md Normal file
View File

@@ -0,0 +1,10 @@
This directory contains integration tests that exercise tea against external services or external executables.
- Unit tests stay next to the packages they cover.
- Integration tests live under `tests/` so they can be run separately.
Common targets:
- `make unit-test`
- `make integration-test`
- `make test`

View File

@@ -3,7 +3,7 @@
//go:build unix //go:build unix
package config package integration
import ( import (
"fmt" "fmt"
@@ -12,10 +12,11 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"time" "time"
"code.gitea.io/tea/modules/config"
) )
func TestConfigLock_CrossProcess(t *testing.T) { func TestConfigLock_CrossProcess(t *testing.T) {
// Create a temp directory for test
tmpDir, err := os.MkdirTemp("", "tea-lock-test") tmpDir, err := os.MkdirTemp("", "tea-lock-test")
if err != nil { if err != nil {
t.Fatalf("failed to create temp dir: %v", err) t.Fatalf("failed to create temp dir: %v", err)
@@ -24,15 +25,16 @@ func TestConfigLock_CrossProcess(t *testing.T) {
lockPath := filepath.Join(tmpDir, "config.yml.lock") lockPath := filepath.Join(tmpDir, "config.yml.lock")
// Acquire lock in main process unlock, err := config.AcquireConfigLockForTesting(lockPath, 5*time.Second)
unlock, err := acquireConfigLock(lockPath, 5*time.Second)
if err != nil { if err != nil {
t.Fatalf("failed to acquire lock: %v", err) t.Fatalf("failed to acquire lock: %v", err)
} }
defer unlock() defer func() {
if err := unlock(); err != nil {
t.Fatalf("failed to release lock: %v", err)
}
}()
// Spawn a subprocess that tries to acquire the same lock
// The subprocess should fail to acquire within timeout
script := fmt.Sprintf(` script := fmt.Sprintf(`
package main package main
@@ -48,19 +50,16 @@ func main() {
} }
defer file.Close() defer file.Close()
// Try non-blocking lock
err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
if err != nil { if err != nil {
// Lock is held - expected behavior
os.Exit(0) os.Exit(0)
} }
// Lock was acquired - unexpected
syscall.Flock(int(file.Fd()), syscall.LOCK_UN) syscall.Flock(int(file.Fd()), syscall.LOCK_UN)
os.Exit(1) os.Exit(1)
} }
`, lockPath) `, lockPath)
// Write and run the test script
scriptPath := filepath.Join(tmpDir, "locktest.go") scriptPath := filepath.Join(tmpDir, "locktest.go")
if err := os.WriteFile(scriptPath, []byte(script), 0o600); err != nil { if err := os.WriteFile(scriptPath, []byte(script), 0o600); err != nil {
t.Fatalf("failed to write test script: %v", err) t.Fatalf("failed to write test script: %v", err)
@@ -78,5 +77,4 @@ func main() {
t.Errorf("subprocess execution failed: %v", err) t.Errorf("subprocess execution failed: %v", err)
} }
} }
// Exit code 0 means lock was properly held - success
} }

View File

@@ -0,0 +1,59 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"os"
"os/exec"
"testing"
"code.gitea.io/tea/modules/config"
teacontext "code.gitea.io/tea/modules/context"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v3"
)
func TestInitCommand_WithRepoSlugSkipsLocalRepoDetection(t *testing.T) {
tmpDir := t.TempDir()
config.SetConfigForTesting(config.LocalConfig{
Logins: []config.Login{{
Name: "test-login",
URL: "https://gitea.example.com",
Token: "token",
User: "login-user",
Default: true,
}},
})
cmd := exec.Command("git", "init", "--object-format=sha256", tmpDir)
cmd.Env = os.Environ()
require.NoError(t, cmd.Run())
oldWd, err := os.Getwd()
require.NoError(t, err)
require.NoError(t, os.Chdir(tmpDir))
t.Cleanup(func() {
require.NoError(t, os.Chdir(oldWd))
})
cliCmd := cli.Command{
Name: "branches",
Flags: []cli.Flag{
&cli.StringFlag{Name: "login"},
&cli.StringFlag{Name: "repo"},
&cli.StringFlag{Name: "remote"},
&cli.StringFlag{Name: "output"},
},
}
require.NoError(t, cliCmd.Set("repo", "owner/repo"))
ctx, err := teacontext.InitCommand(&cliCmd)
require.NoError(t, err)
require.Equal(t, "owner", ctx.Owner)
require.Equal(t, "repo", ctx.Repo)
require.Equal(t, "owner/repo", ctx.RepoSlug)
require.Nil(t, ctx.LocalRepo)
require.NotNil(t, ctx.Login)
require.Equal(t, "test-login", ctx.Login.Name)
}

View File

@@ -1,7 +1,7 @@
// Copyright 2025 The Gitea Authors. All rights reserved. // Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package git package integration
import ( import (
"os" "os"
@@ -9,11 +9,11 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
teagit "code.gitea.io/tea/modules/git"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestRepoFromPath_Worktree(t *testing.T) { func TestRepoFromPath_Worktree(t *testing.T) {
// Create a temporary directory for test
tmpDir, err := os.MkdirTemp("", "tea-worktree-test-*") tmpDir, err := os.MkdirTemp("", "tea-worktree-test-*")
assert.NoError(t, err) assert.NoError(t, err)
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
@@ -21,21 +21,17 @@ func TestRepoFromPath_Worktree(t *testing.T) {
mainRepoPath := filepath.Join(tmpDir, "main-repo") mainRepoPath := filepath.Join(tmpDir, "main-repo")
worktreePath := filepath.Join(tmpDir, "worktree") worktreePath := filepath.Join(tmpDir, "worktree")
// Initialize main repository
cmd := exec.Command("git", "init", mainRepoPath) cmd := exec.Command("git", "init", mainRepoPath)
assert.NoError(t, cmd.Run()) assert.NoError(t, cmd.Run())
// Configure git for the test
cmd = exec.Command("git", "-C", mainRepoPath, "config", "user.email", "test@example.com") cmd = exec.Command("git", "-C", mainRepoPath, "config", "user.email", "test@example.com")
assert.NoError(t, cmd.Run()) assert.NoError(t, cmd.Run())
cmd = exec.Command("git", "-C", mainRepoPath, "config", "user.name", "Test User") cmd = exec.Command("git", "-C", mainRepoPath, "config", "user.name", "Test User")
assert.NoError(t, cmd.Run()) assert.NoError(t, cmd.Run())
// Add a remote to the main repository
cmd = exec.Command("git", "-C", mainRepoPath, "remote", "add", "origin", "https://gitea.com/owner/repo.git") cmd = exec.Command("git", "-C", mainRepoPath, "remote", "add", "origin", "https://gitea.com/owner/repo.git")
assert.NoError(t, cmd.Run()) assert.NoError(t, cmd.Run())
// Create an initial commit (required for worktree)
readmePath := filepath.Join(mainRepoPath, "README.md") readmePath := filepath.Join(mainRepoPath, "README.md")
err = os.WriteFile(readmePath, []byte("# Test Repo\n"), 0o644) err = os.WriteFile(readmePath, []byte("# Test Repo\n"), 0o644)
assert.NoError(t, err) assert.NoError(t, err)
@@ -44,19 +40,14 @@ func TestRepoFromPath_Worktree(t *testing.T) {
cmd = exec.Command("git", "-C", mainRepoPath, "commit", "-m", "Initial commit") cmd = exec.Command("git", "-C", mainRepoPath, "commit", "-m", "Initial commit")
assert.NoError(t, cmd.Run()) assert.NoError(t, cmd.Run())
// Create a worktree
cmd = exec.Command("git", "-C", mainRepoPath, "worktree", "add", worktreePath, "-b", "test-branch") cmd = exec.Command("git", "-C", mainRepoPath, "worktree", "add", worktreePath, "-b", "test-branch")
assert.NoError(t, cmd.Run()) assert.NoError(t, cmd.Run())
// Test: Open repository from worktree path repo, err := teagit.RepoFromPath(worktreePath)
repo, err := RepoFromPath(worktreePath)
assert.NoError(t, err, "Should be able to open worktree") assert.NoError(t, err, "Should be able to open worktree")
// Test: Read config from worktree (should read from main repo's config)
config, err := repo.Config() config, err := repo.Config()
assert.NoError(t, err, "Should be able to read config") assert.NoError(t, err, "Should be able to read config")
// Verify that remotes are accessible from worktree
assert.NotEmpty(t, config.Remotes, "Should be able to read remotes from worktree") assert.NotEmpty(t, config.Remotes, "Should be able to read remotes from worktree")
assert.Contains(t, config.Remotes, "origin", "Should have origin remote") assert.Contains(t, config.Remotes, "origin", "Should have origin remote")
assert.Equal(t, "https://gitea.com/owner/repo.git", config.Remotes["origin"].URLs[0], "Should have correct remote URL") assert.Equal(t, "https://gitea.com/owner/repo.git", config.Remotes["origin"].URLs[0], "Should have correct remote URL")

View File

@@ -1,28 +1,66 @@
// Copyright 2025 The Gitea Authors. All rights reserved. // Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package repos package integration
import ( import (
"context" "context"
"fmt" "fmt"
"os" "os"
"path/filepath"
"testing" "testing"
"time" "time"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"code.gitea.io/tea/cmd/repos"
"code.gitea.io/tea/modules/config"
"code.gitea.io/tea/modules/task" "code.gitea.io/tea/modules/task"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
func useTempConfigPath(t *testing.T) string {
t.Helper()
configPath := filepath.Join(t.TempDir(), "config.yml")
config.SetConfigPathForTesting(configPath)
t.Cleanup(func() {
config.SetConfigPathForTesting("")
})
return configPath
}
func createIntegrationLogin(t *testing.T, giteaURL string) *config.Login {
t.Helper()
_ = useTempConfigPath(t)
username := os.Getenv("GITEA_TEA_TEST_USERNAME")
password := os.Getenv("GITEA_TEA_TEST_PASSWORD")
require.NotEmpty(t, username, "GITEA_TEA_TEST_USERNAME is required for integration tests")
require.NotEmpty(t, password, "GITEA_TEA_TEST_PASSWORD is required for integration tests")
require.NoError(t, task.CreateLogin("integration", "", username, password, "", "", "", giteaURL, "", "", true, false, false, false))
login, err := config.GetLoginByName("integration")
require.NoError(t, err)
require.NotNil(t, login)
return login
}
func TestCreateRepoObjectFormat(t *testing.T) { func TestCreateRepoObjectFormat(t *testing.T) {
giteaURL := os.Getenv("GITEA_TEA_TEST_URL") giteaURL := os.Getenv("GITEA_TEA_TEST_URL")
if giteaURL == "" { if giteaURL == "" {
t.Skip("GITEA_TEA_TEST_URL is not set, skipping test") t.Skip("GITEA_TEA_TEST_URL is not set, skipping test")
} }
login := createIntegrationLogin(t, giteaURL)
client := login.Client()
timestamp := time.Now().Unix() timestamp := time.Now().Unix()
tests := []struct { tests := []struct {
name string name string
args []string args []string
@@ -56,22 +94,15 @@ func TestCreateRepoObjectFormat(t *testing.T) {
}, },
} }
giteaUserName := os.Getenv("GITEA_TEA_TEST_USERNAME")
giteaUserPasword := os.Getenv("GITEA_TEA_TEST_PASSWORD")
err := task.CreateLogin("test", "", giteaUserName, giteaUserPasword, "", "", "", giteaURL, "", "", true, false, false, false)
if err != nil && err.Error() != "login name 'test' has already been used" {
t.Fatal(err)
}
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
reposCmd := &cli.Command{ reposCmd := &cli.Command{
Name: "repos", Name: "repos",
Commands: []*cli.Command{&CmdRepoCreate}, Commands: []*cli.Command{&repos.CmdRepoCreate},
} }
tt.args = append(tt.args, "--login", "test")
args := append([]string{"repos", "create"}, tt.args...) args := append([]string{"repos", "create"}, tt.args...)
args = append(args, "--login", login.Name)
err := reposCmd.Run(context.Background(), args) err := reposCmd.Run(context.Background(), args)
if tt.wantErr { if tt.wantErr {
@@ -82,7 +113,12 @@ func TestCreateRepoObjectFormat(t *testing.T) {
return return
} }
assert.NoError(t, err) require.NoError(t, err)
t.Cleanup(func() {
if _, delErr := client.DeleteRepo(login.User, tt.wantOpts.Name); delErr != nil {
t.Logf("failed to delete integration test repo %q: %v", tt.wantOpts.Name, delErr)
}
})
}) })
} }
} }