Files
silverwind 1031e34987 test: run integration tests on random ports and speed up slow tests (#39842)
Integration test processes now get their own work directory, config file
and random ports, so several runs can execute in parallel on one
machine. The slowest tests also lose fixed sleeps and needlessly
expensive setup, with their assertions kept.

- Batch PostgreSQL sequence resets, the main per-test fixture cost there
- Cheap hashing params in hash and OAuth2 fixture tests, full-cost
vectors stay
- Single fetch for negative Actions runner assertions instead of 1s
polls
- Queue flushes and backdated timestamps instead of sleeps
- In-memory SQLite test journal, tmpfs for PostgreSQL in CI
- E2E instance writes SSH keys into its work directory instead of
`~/.ssh/authorized_keys`
2026-10-18 12:56:04 +00:00

265 lines
9.2 KiB
Go

// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/url"
"testing"
auth_model "gitea.dev/models/auth"
"gitea.dev/models/db"
git_model "gitea.dev/models/git"
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unittest"
user_model "gitea.dev/models/user"
"gitea.dev/modules/git"
"gitea.dev/modules/git/gitcmd"
"gitea.dev/modules/setting"
"gitea.dev/modules/test"
repo_service "gitea.dev/services/repository"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGitPush(t *testing.T) {
onGiteaRun(t, testGitPush)
}
func testGitPush(t *testing.T, u *url.URL) {
t.Run("Push branches at once", func(t *testing.T) {
runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) {
for i := range 4 {
branchName := fmt.Sprintf("branch-%d", i)
pushed = append(pushed, branchName)
doGitCreateBranch(gitPath, branchName)(t)
}
pushed = append(pushed, "master")
doGitPushTestRepository(gitPath, "origin", "--all")(t)
return pushed, deleted
})
})
t.Run("Push branches exists", func(t *testing.T) {
runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) {
for i := range 4 {
branchName := fmt.Sprintf("branch-%d", i)
if i < 2 {
pushed = append(pushed, branchName)
}
doGitCreateBranch(gitPath, branchName)(t)
}
pushed = append(pushed, "master")
args := append([]string{"origin"}, pushed...)
doGitPushTestRepository(gitPath, args...)(t)
pushed = pushed[:0]
for i := range 2 {
branchName := fmt.Sprintf("branch-%d", i)
pushed = append(pushed, branchName)
doGitCheckoutWriteFileCommit(localGitAddCommitOptions{
LocalRepoPath: gitPath,
CheckoutBranch: branchName,
TreeFilePath: fmt.Sprintf("file-%s.txt", branchName),
TreeFileContent: "file " + branchName,
})(t)
}
for i := 2; i < 4; i++ {
pushed = append(pushed, fmt.Sprintf("branch-%d", i))
}
pushed = append(pushed, "master")
// push all, so that master is not changed
doGitPushTestRepository(gitPath, "origin", "--all")(t)
return pushed, deleted
})
})
t.Run("Push branches one by one", func(t *testing.T) {
runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) {
for i := range 4 {
branchName := fmt.Sprintf("branch-%d", i)
doGitCreateBranch(gitPath, branchName)(t)
doGitPushTestRepository(gitPath, "origin", branchName)(t)
pushed = append(pushed, branchName)
}
return pushed, deleted
})
})
t.Run("Push branch with options", func(t *testing.T) {
runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) {
branchName := "branch-with-options"
doGitCreateBranch(gitPath, branchName)(t)
doGitPushTestRepository(gitPath, "origin", branchName, "-o", "repo.private=true", "-o", "repo.template=true")(t)
pushed = append(pushed, branchName)
return pushed, deleted
})
})
t.Run("Delete branches", func(t *testing.T) {
runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) {
doGitPushTestRepository(gitPath, "origin", "master")(t) // make sure master is the default branch instead of a branch we are going to delete
pushed = append(pushed, "master")
for i := range 4 {
branchName := fmt.Sprintf("branch-%d", i)
pushed = append(pushed, branchName)
doGitCreateBranch(gitPath, branchName)(t)
}
doGitPushTestRepository(gitPath, "origin", "--all")(t)
for i := range 2 {
branchName := fmt.Sprintf("branch-%d", i)
doGitPushTestRepository(gitPath, "origin", "--delete", branchName)(t)
deleted = append(deleted, branchName)
}
return pushed, deleted
})
})
t.Run("Push to deleted branch", func(t *testing.T) {
runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) {
doGitPushTestRepository(gitPath, "origin", "master")(t) // make sure master is the default branch instead of a branch we are going to delete
pushed = append(pushed, "master")
doGitCreateBranch(gitPath, "branch-1")(t)
doGitPushTestRepository(gitPath, "origin", "branch-1")(t)
pushed = append(pushed, "branch-1")
// delete and restore
doGitPushTestRepository(gitPath, "origin", "--delete", "branch-1")(t)
doGitPushTestRepository(gitPath, "origin", "branch-1")(t)
return pushed, deleted
})
})
}
func TestGitPushVisibilityOption(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo, err := repo_service.CreateRepository(t.Context(), user, user, repo_service.CreateRepoOptions{
Name: "repo-visibility-option",
AutoInit: false,
DefaultBranch: "master",
IsPrivate: false,
})
require.NoError(t, err)
require.NotEmpty(t, repo)
gitPath := t.TempDir()
doGitInitTestRepository(gitPath)(t)
oldPath, oldUser := u.Path, u.User
defer func() { u.Path, u.User = oldPath, oldUser }()
u.Path = repo.FullName() + ".git"
u.User = url.UserPassword(user.LowerName, userPassword)
doGitAddRemote(gitPath, "origin", u)(t)
// The first push into an empty repository is a "push-to-create", so the
// repo.private push option is honored to set the initial visibility.
doGitPushTestRepository(gitPath, "origin", "master", "-o", "repo.private=true")(t)
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID})
assert.True(t, repo.IsPrivate, "repo.private option should apply on push-to-create")
// The repository is now populated; a later push must NOT silently flip
// visibility, otherwise a repo admin could change it bypassing the audit
// trail, webhooks, and notifications a proper settings change would fire.
doGitCreateBranch(gitPath, "branch2")(t)
doGitPushTestRepository(gitPath, "origin", "branch2", "-o", "repo.private=false")(t)
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID})
assert.True(t, repo.IsPrivate, "repo.private option must be ignored on an existing repository")
defer test.MockVariableValue(&setting.Repository.ForcePrivate, true)()
forcedRepo, err := repo_service.CreateRepository(t.Context(), user, user, repo_service.CreateRepoOptions{Name: "repo-visibility-forced", DefaultBranch: "master", IsPrivate: true})
require.NoError(t, err)
u.Path = forcedRepo.FullName() + ".git"
doGitAddRemote(gitPath, "forced", u)(t)
doGitPushTestRepository(gitPath, "forced", "master", "-o", "repo.private=false")(t)
assert.True(t, unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: forcedRepo.ID}).IsPrivate)
})
}
func runTestGitPush(t *testing.T, u *url.URL, gitOperation func(t *testing.T, gitPath string) (pushed, deleted []string)) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo, err := repo_service.CreateRepository(t.Context(), user, user, repo_service.CreateRepoOptions{
Name: "repo-to-push",
Description: "test git push",
AutoInit: false,
DefaultBranch: "main",
IsPrivate: false,
})
require.NoError(t, err)
require.NotEmpty(t, repo)
gitPath := t.TempDir()
doGitInitTestRepository(gitPath)(t)
oldPath := u.Path
oldUser := u.User
defer func() {
u.Path = oldPath
u.User = oldUser
}()
u.Path = repo.FullName() + ".git"
u.User = url.UserPassword(user.LowerName, userPassword)
doGitAddRemote(gitPath, "origin", u)(t)
gitRepo, err := git.OpenRepositoryLocal(t.Context(), gitPath)
require.NoError(t, err)
defer gitRepo.Close()
pushedBranches, deletedBranches := gitOperation(t, gitPath)
dbBranches := make([]*git_model.Branch, 0)
require.NoError(t, db.GetEngine(t.Context()).Where("repo_id=?", repo.ID).Find(&dbBranches))
assert.Lenf(t, dbBranches, len(pushedBranches), "mismatched number of branches in db")
dbBranchesMap := make(map[string]*git_model.Branch, len(dbBranches))
for _, branch := range dbBranches {
dbBranchesMap[branch.Name] = branch
}
deletedBranchesMap := make(map[string]bool, len(deletedBranches))
for _, branchName := range deletedBranches {
deletedBranchesMap[branchName] = true
}
for _, branchName := range pushedBranches {
branch, ok := dbBranchesMap[branchName]
deleted := deletedBranchesMap[branchName]
assert.True(t, ok, "branch %s not found in database", branchName)
assert.Equal(t, deleted, branch.IsDeleted, "IsDeleted of %s is %v, but it's expected to be %v", branchName, branch.IsDeleted, deleted)
commitID, err := gitRepo.GetBranchCommitID(t.Context(), branchName)
require.NoError(t, err)
assert.Equal(t, commitID, branch.CommitID)
}
require.NoError(t, repo_service.DeleteRepositoryDirectly(t.Context(), repo.ID))
}
func TestPushPullRefs(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
baseAPITestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
u.Path = baseAPITestContext.GitPath()
u.User = url.UserPassword("user2", userPassword)
dstPath := t.TempDir()
doGitClone(dstPath, u)(t)
cmd := gitcmd.NewCommand("push", "--delete", "origin", "refs/pull/2/head")
stdout, stderr, err := cmd.WithDir(dstPath).RunStdString(t.Context())
assert.Error(t, err)
assert.Empty(t, stdout)
assert.NotContains(t, stderr, "[deleted]", "stderr: %s", stderr)
})
}