summaryrefslogtreecommitdiffstats
path: root/modules/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
committerDaniel Baumann <daniel@debian.org>2024-12-12 23:57:56 +0100
commite68b9d00a6e05b3a941f63ffb696f91e554ac5ec (patch)
tree97775d6c13b0f416af55314eb6a89ef792474615 /modules/test
parentInitial commit. (diff)
downloadforgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.tar.xz
forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.zip
Adding upstream version 9.0.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to '')
-rw-r--r--modules/test/logchecker.go107
-rw-r--r--modules/test/logchecker_test.go58
-rw-r--r--modules/test/utils.go48
-rw-r--r--modules/test/utils_test.go18
-rw-r--r--modules/testlogger/testlogger.go580
5 files changed, 811 insertions, 0 deletions
diff --git a/modules/test/logchecker.go b/modules/test/logchecker.go
new file mode 100644
index 0000000..0f12257
--- /dev/null
+++ b/modules/test/logchecker.go
@@ -0,0 +1,107 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package test
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "code.gitea.io/gitea/modules/log"
+)
+
+type LogChecker struct {
+ *log.EventWriterBaseImpl
+
+ filterMessages []string
+ filtered []bool
+
+ stopMark string
+ stopped bool
+
+ mu sync.Mutex
+}
+
+func (lc *LogChecker) Run(ctx context.Context) {
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case event, ok := <-lc.Queue:
+ if !ok {
+ return
+ }
+ lc.checkLogEvent(event)
+ }
+ }
+}
+
+func (lc *LogChecker) checkLogEvent(event *log.EventFormatted) {
+ lc.mu.Lock()
+ defer lc.mu.Unlock()
+ for i, msg := range lc.filterMessages {
+ if strings.Contains(event.Origin.MsgSimpleText, msg) {
+ lc.filtered[i] = true
+ }
+ }
+ if strings.Contains(event.Origin.MsgSimpleText, lc.stopMark) {
+ lc.stopped = true
+ }
+}
+
+var checkerIndex int64
+
+func NewLogChecker(namePrefix string, level log.Level) (logChecker *LogChecker, cancel func()) {
+ logger := log.GetManager().GetLogger(namePrefix)
+ newCheckerIndex := atomic.AddInt64(&checkerIndex, 1)
+ writerName := namePrefix + "-" + fmt.Sprint(newCheckerIndex)
+
+ lc := &LogChecker{}
+ lc.EventWriterBaseImpl = log.NewEventWriterBase(writerName, "test-log-checker", log.WriterMode{
+ Level: level,
+ })
+ logger.AddWriters(lc)
+ return lc, func() { _ = logger.RemoveWriter(writerName) }
+}
+
+// Filter will make the `Check` function to check if these logs are outputted.
+func (lc *LogChecker) Filter(msgs ...string) *LogChecker {
+ lc.mu.Lock()
+ defer lc.mu.Unlock()
+ lc.filterMessages = make([]string, len(msgs))
+ copy(lc.filterMessages, msgs)
+ lc.filtered = make([]bool, len(lc.filterMessages))
+ return lc
+}
+
+func (lc *LogChecker) StopMark(msg string) *LogChecker {
+ lc.mu.Lock()
+ defer lc.mu.Unlock()
+ lc.stopMark = msg
+ lc.stopped = false
+ return lc
+}
+
+// Check returns the filtered slice and whether the stop mark is reached.
+func (lc *LogChecker) Check(d time.Duration) (filtered []bool, stopped bool) {
+ stop := time.Now().Add(d)
+
+ for {
+ lc.mu.Lock()
+ stopped = lc.stopped
+ lc.mu.Unlock()
+
+ if time.Now().After(stop) || stopped {
+ lc.mu.Lock()
+ f := make([]bool, len(lc.filtered))
+ copy(f, lc.filtered)
+ lc.mu.Unlock()
+ return f, stopped
+ }
+ time.Sleep(10 * time.Millisecond)
+ }
+}
diff --git a/modules/test/logchecker_test.go b/modules/test/logchecker_test.go
new file mode 100644
index 0000000..0f410fe
--- /dev/null
+++ b/modules/test/logchecker_test.go
@@ -0,0 +1,58 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package test
+
+import (
+ "testing"
+ "time"
+
+ "code.gitea.io/gitea/modules/log"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestLogCheckerInfo(t *testing.T) {
+ lc, cleanup := NewLogChecker(log.DEFAULT, log.INFO)
+ defer cleanup()
+
+ lc.Filter("First", "Third").StopMark("End")
+ log.Info("test")
+
+ filtered, stopped := lc.Check(100 * time.Millisecond)
+ assert.ElementsMatch(t, []bool{false, false}, filtered)
+ assert.False(t, stopped)
+
+ log.Info("First")
+ log.Debug("Third")
+ filtered, stopped = lc.Check(100 * time.Millisecond)
+ assert.ElementsMatch(t, []bool{true, false}, filtered)
+ assert.False(t, stopped)
+
+ log.Info("Second")
+ log.Debug("Third")
+ filtered, stopped = lc.Check(100 * time.Millisecond)
+ assert.ElementsMatch(t, []bool{true, false}, filtered)
+ assert.False(t, stopped)
+
+ log.Info("Third")
+ filtered, stopped = lc.Check(100 * time.Millisecond)
+ assert.ElementsMatch(t, []bool{true, true}, filtered)
+ assert.False(t, stopped)
+
+ log.Info("End")
+ filtered, stopped = lc.Check(100 * time.Millisecond)
+ assert.ElementsMatch(t, []bool{true, true}, filtered)
+ assert.True(t, stopped)
+}
+
+func TestLogCheckerDebug(t *testing.T) {
+ lc, cleanup := NewLogChecker(log.DEFAULT, log.DEBUG)
+ defer cleanup()
+
+ lc.StopMark("End")
+
+ log.Debug("End")
+ _, stopped := lc.Check(100 * time.Millisecond)
+ assert.True(t, stopped)
+}
diff --git a/modules/test/utils.go b/modules/test/utils.go
new file mode 100644
index 0000000..3d884b6
--- /dev/null
+++ b/modules/test/utils.go
@@ -0,0 +1,48 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package test
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "strings"
+
+ "code.gitea.io/gitea/modules/json"
+)
+
+// RedirectURL returns the redirect URL of a http response.
+// It also works for JSONRedirect: `{"redirect": "..."}`
+func RedirectURL(resp http.ResponseWriter) string {
+ loc := resp.Header().Get("Location")
+ if loc != "" {
+ return loc
+ }
+ if r, ok := resp.(*httptest.ResponseRecorder); ok {
+ m := map[string]any{}
+ err := json.Unmarshal(r.Body.Bytes(), &m)
+ if err == nil {
+ if loc, ok := m["redirect"].(string); ok {
+ return loc
+ }
+ }
+ }
+ return ""
+}
+
+func IsNormalPageCompleted(s string) bool {
+ return strings.Contains(s, `<footer class="page-footer"`) && strings.Contains(s, `</html>`)
+}
+
+// use for global variables only
+func MockVariableValue[T any](p *T, v T) (reset func()) {
+ old := *p
+ *p = v
+ return func() { *p = old }
+}
+
+// use for global variables only
+func MockProtect[T any](p *T) (reset func()) {
+ old := *p
+ return func() { *p = old }
+}
diff --git a/modules/test/utils_test.go b/modules/test/utils_test.go
new file mode 100644
index 0000000..a3be74e
--- /dev/null
+++ b/modules/test/utils_test.go
@@ -0,0 +1,18 @@
+// Copyright 2024 The Forgejo Authors
+// SPDX-License-Identifier: MIT
+
+package test
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMockProtect(t *testing.T) {
+ mockable := "original"
+ restore := MockProtect(&mockable)
+ mockable = "tainted"
+ restore()
+ assert.Equal(t, "original", mockable)
+}
diff --git a/modules/testlogger/testlogger.go b/modules/testlogger/testlogger.go
new file mode 100644
index 0000000..7897cc6
--- /dev/null
+++ b/modules/testlogger/testlogger.go
@@ -0,0 +1,580 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package testlogger
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "runtime"
+ "strings"
+ "sync"
+ "testing"
+ "time"
+
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/queue"
+)
+
+var (
+ prefix string
+ SlowTest = 10 * time.Second
+ SlowFlush = 5 * time.Second
+)
+
+var WriterCloser = &testLoggerWriterCloser{}
+
+type testLoggerWriterCloser struct {
+ sync.RWMutex
+ t []testing.TB
+ errs []error // stack of error, parallel to the stack of testing.TB
+ err error // fallback if the stack is empty
+}
+
+func (w *testLoggerWriterCloser) pushT(t testing.TB) {
+ w.Lock()
+ w.t = append(w.t, t)
+ w.errs = append(w.errs, nil)
+ w.Unlock()
+}
+
+func (w *testLoggerWriterCloser) Log(level log.Level, msg string) {
+ msg = strings.TrimSpace(msg)
+
+ w.printMsg(msg)
+ if level >= log.ERROR {
+ w.recordError(msg)
+ }
+}
+
+// list of error message which will not fail the test
+// ideally this list should be empty, however ensuring that it does not grow
+// is already a good first step.
+var ignoredErrorMessage = []string{
+ // only seen on mysql tests https://codeberg.org/forgejo/forgejo/pulls/2657#issuecomment-1693055
+ `table columns using inconsistent collation, they should use "utf8mb4_0900_ai_ci". Please go to admin panel Self Check page`,
+
+ // TestPullWIPConvertSidebar
+ `:PullRequestPushCommits() [E] comment.LoadIssue: issue does not exist [id:`,
+
+ // TestAPIDeleteReleaseByTagName
+ // action notification were a commit cannot be computed (because the commit got deleted)
+ `Notify() [E] an error occurred while executing the DeleteRelease actions method: gitRepo.GetCommit: object does not exist [id: refs/tags/release-tag, rel_path: ]`,
+ `Notify() [E] an error occurred while executing the PushCommits actions method: gitRepo.GetCommit: object does not exist [id: refs/tags/release-tag, rel_path: ]`,
+
+ // TestAPIRepoTags
+ `Notify() [E] an error occurred while executing the DeleteRelease actions method: gitRepo.GetCommit: object does not exist [id: refs/tags/gitea/22, rel_path: ]`,
+ `Notify() [E] an error occurred while executing the PushCommits actions method: gitRepo.GetCommit: object does not exist [id: refs/tags/gitea/22, rel_path: ]`,
+
+ // TestAPIDeleteTagByName
+ `Notify() [E] an error occurred while executing the DeleteRelease actions method: gitRepo.GetCommit: object does not exist [id: refs/tags/delete-tag, rel_path: ]`,
+ `Notify() [E] an error occurred while executing the PushCommits actions method: gitRepo.GetCommit: object does not exist [id: refs/tags/delete-tag, rel_path: ]`,
+
+ // TestAPIGenerateRepo
+ `Notify() [E] an error occurred while executing the CreateRepository actions method: gitRepo.GetCommit: object does not exist [id: , rel_path: ]`,
+
+ // TestAPIPullUpdateByRebase
+ `:testPR() [E] Unable to GetPullRequestByID[`,
+ `:PullRequestSynchronized() [E] LoadAttributes: getRepositoryByID `,
+ `:PullRequestSynchronized() [E] pr.Issue.LoadRepo: getRepositoryByID [`,
+ `:handler() [E] Was unable to create issue notification: issue does not exist [`,
+ `:func1() [E] PullRequestList.LoadAttributes: issues and prs may be not in sync: cannot find issue`,
+ `:func1() [E] checkForInvalidation: GetRepositoryByIDCtx: repository does not exist `,
+
+ // TestAPIPullReview
+ `PullRequestReview() [E] Unsupported review webhook type`,
+
+ // TestAPIPullReviewRequest
+ `ToAPIPullRequest() [E] unable to resolve PR head ref`,
+
+ // TestAPILFSUpload
+ `Put() [E] Whilst putting LFS OID[ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb]: Failed to copy to tmpPath: ca/97/8112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb Error: content size does not match`,
+ `[E] Error putting LFS MetaObject [ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb] into content store. Error: content size does not match`,
+ `UploadHandler() [E] Upload does not match LFS MetaObject [ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb]. Error: content size does not match`,
+ `Put() [E] Whilst putting LFS OID[2581dd7bbc1fe44726de4b7dd806a087a978b9c5aec0a60481259e34be09b06a]: Failed to copy to tmpPath: 25/81/dd7bbc1fe44726de4b7dd806a087a978b9c5aec0a60481259e34be09b06a Error: content hash does not match OID`,
+ `[E] Error putting LFS MetaObject [2581dd7bbc1fe44726de4b7dd806a087a978b9c5aec0a60481259e34be09b06a] into content store. Error: content hash does not match OID`,
+ `UploadHandler() [E] Upload does not match LFS MetaObject [2581dd7bbc1fe44726de4b7dd806a087a978b9c5aec0a60481259e34be09b06a]. Error: content hash does not match OID`,
+ `UploadHandler() [E] Upload does not match LFS MetaObject [83de2e488b89a0aa1c97496b888120a28b0c1e15463a4adb8405578c540f36d4]. Error: content size does not match`,
+
+ // TestAPILFSVerify
+ `getAuthenticatedMeta() [E] Unable to get LFS OID[fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042] Error: LFS Meta object does not exist`,
+
+ // TestAPIUpdateOrgAvatar
+ `UpdateAvatar() [E] UploadAvatar: image.DecodeConfig: image: unknown format`,
+
+ // TestGetAttachment
+ `/data/attachments/a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18: no such file or directory`,
+
+ // TestBlockUser
+ `BlockedUsersUnblock() [E] IsOrganization: org3 is an organization not a user`,
+ `BlockedUsersBlock() [E] IsOrganization: org3 is an organization not a user`,
+ `Action() [E] Cannot perform this action on an organization "unblock"`,
+ `Action() [E] Cannot perform this action on an organization "block"`,
+
+ // TestBlockActions
+ `/gitea-repositories/user10/repo7.git Error: no such file or directory`,
+
+ // TestRebuildCargo
+ `RebuildCargoIndex() [E] RebuildIndex failed: GetRepositoryByOwnerAndName: repository does not exist [id: 0, uid: 0, owner_name: user2, name: _cargo-index]`,
+
+ // TestDangerZoneConfirmation/Convert_fork/Fail
+ `/gitea-repositories/user20/big_test_public_fork_7.git Error: no such file or directory`,
+ // TestGitSmartHTTP
+ `:sendFile() [E] request file path contains invalid path: objects/info/..\..\..\..\custom\conf\app.ini`,
+ // TestGit/HTTP/BranchProtectMerge
+ `:SSHLog() [E] ssh: Not allowed to push to protected branch protected. HookPreReceive(last) failed: internal API error response, status=403`,
+ // TestGit/HTTP/BranchProtectMerge
+ `:SSHLog() [E] ssh: Not allowed to push to protected branch protected. HookPreReceive(last) failed: internal API error response, status=403`,
+ // TestGit/HTTP/BranchProtectMerge
+ `:SSHLog() [E] ssh: branch protected is protected from force push. HookPreReceive(last) failed: internal API error response, status=403`,
+ // TestGit/HTTP/BranchProtect
+ `:SSHLog() [E] ssh: branch before-create-2 is protected from changing file protected-file-data-`,
+ // TestGit/HTTP/MergeFork/CreatePRAndMerge
+ `:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1099 name: user2:master]`, // sqlite
+ "s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 10000 name: user2:master]", // mysql
+ "s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1060 name: user2:master]", // pgsql
+ // TestGit/HTTP/BranchProtectMerge
+ `:func1() [E] PushToBaseRepo: PushRejected Error: exit status 1 - remote: error: cannot lock ref`,
+ // TestGit/SSH/BranchProtectMerge
+ `:func1() [E] PushToBaseRepo: PushRejected Error: exit status 1 - remote: error: cannot lock ref`,
+ // TestGit/SSH/LFS/PushCommit/Little
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/LFS/PushCommit/Little
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/LFS/PushCommit/Big
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/LFS/PushCommit/Big
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/LFS/Locks
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/LFS/Locks
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/LFS/Locks
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/LFS/Locks
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/LFS/Locks
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/PushParams/NoParams
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/PushParams/NoParams
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/PushParams/TitleOverride
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/PushParams/TitleOverride
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/PushParams/DescriptionOverride
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/PushParams/DescriptionOverride
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/Force_push/Fails
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/Force_push/Fails
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/Force_push/Succeeds
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/Force_push/Succeeds
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/Force_push
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/Force_push
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/Branch_already_contains_commit
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/Branch_already_contains_commit
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Not allowed to push to protected branch protected. HookPreReceive(last) failed: internal API error response, status=403`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: branch protected is protected from force push. HookPreReceive(last) failed: internal API error response, status=403`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/MergeFork/CreatePRAndMerge
+ `:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1102 name: user2:master]`, // sqlite
+ "s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 10003 name: user2:master]", // mysql
+ "s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1063 name: user2:master]", // pgsql
+ // TestGit/SSH/PushCreate
+ `:SSHLog() [E] ssh: Push to create is not enabled for users. ServCommand failed: internal API error response, status=403`,
+ // TestGit/SSH/PushCreate
+ `:SSHLog() [E] ssh: Cannot find repository: user2/repo-tmp-push-create-ssh. ServCommand failed: internal API error response, status=404`,
+ // TestGit/SSH/PushCreate
+ `:SSHLog() [E] ssh: Invalid repo name. Invalid repo name: invalid/repo-tmp-push-create-ssh`,
+ // TestIssueReaction
+ `:ChangeIssueReaction() [E] ChangeIssueReaction: '8ball' is not an allowed reaction`,
+ // TestIssuePinMove
+ `:IssuePinMove() [E] Issue does not belong to this repository`,
+ // TestLinksLogin
+ `:GetIssuesAllCommitStatus() [E] getAllCommitStatus: can't get commit statuses of pull [6]: object does not exist [id: refs/pull/2/head, rel_path: ]`,
+ // TestLinksLogin
+ `:GetIssuesAllCommitStatus() [E] getAllCommitStatus: can't get commit statuses of pull [6]: object does not exist [id: refs/pull/2/head, rel_path: ]`,
+ // TestLinksLogin
+ `:GetIssuesAllCommitStatus() [E] getAllCommitStatus: can't get commit statuses of pull [6]: object does not exist [id: refs/pull/2/head, rel_path: ]`,
+ // TestLinksLogin
+ `:GetIssuesAllCommitStatus() [E] Cannot open git repository <Repository 23:org17/big_test_public_4> for issue #1[20]. Error: no such file or directory`,
+ // TestMigrate
+ `] for OwnerID[2] failed: error while listing issues: token does not have at least one of required scope(s): [read:issue]`,
+ // TestMigrate
+ `:handler() [E] Run task failed: error while listing issues: token does not have at least one of required scope(s): [read:issue]`,
+ // TestMigrate
+ `] for OwnerID[2] failed: error while listing issues: token does not have at least one of required scope(s): [read:issue]`,
+ // TestMigrate
+ `:handler() [E] Run task failed: error while listing issues: token does not have at least one of required scope(s): [read:issue]`,
+ // TestMirrorPush
+ `:GetInfoRefs() [E] fork/exec /usr/bin/git: no such file or directory -`,
+
+ // TestOrgMembers
+ `:loadOrganizationOwners() [E] Organization does not have owner team: 25`,
+ // TestOrgMembers
+ `:loadOrganizationOwners() [E] Organization does not have owner team: 25`,
+ // TestOrgMembers
+ `:loadOrganizationOwners() [E] Organization does not have owner team: 25`,
+ // TestRecentlyPushed/unrelated_branches_are_not_shown
+ `:SyncRepoBranches() [E] OpenRepository[user30/repo50]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestRecentlyPushed/unrelated_branches_are_not_shown
+ `:handlerBranchSync() [E] syncRepoBranches [50] failed: no such file or directory`,
+ // TestRecentlyPushed/unrelated_branches_are_not_shown
+ `:SyncRepoBranches() [E] OpenRepository[user30/repo51]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestRecentlyPushed/unrelated_branches_are_not_shown
+ `:handlerBranchSync() [E] syncRepoBranches [51] failed: no such file or directory`,
+ // TestRecentlyPushed/unrelated_branches_are_not_shown
+ `:SyncRepoBranches() [E] OpenRepository[user2/scoped_label]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestRecentlyPushed/unrelated_branches_are_not_shown
+ `:handlerBranchSync() [E] syncRepoBranches [55] failed: no such file or directory`,
+ // TestCantMergeConflict
+ "]user1/repo1#1[base...conflict]> Unable to merge tracking into base: Merge Conflict Error: exit status 1: \nAuto-merging README.md\nCONFLICT (content): Merge conflict in README.md\nAutomatic merge failed; fix conflicts and then commit the result.",
+
+ // TestCantMergeUnrelated
+ `]user1/repo1#1[base...unrelated]> Unable to merge tracking into base: Merge UnrelatedHistories Error: exit status 128: fatal: refusing to merge unrelated histories`,
+ // TestCantFastForwardOnlyMergeDiverging
+ "]user1/repo1#1[master...diverging]> Unable to merge tracking into base: Merge DivergingFastForwardOnly Error: exit status 128: hint: Diverging branches can't be fast-forwarded, you need to either:\nhint: \nhint: \tgit merge --no-ff\nhint: \nhint: or:\nhint: \nhint: \tgit rebase\nhint: \nhint: Disable this message with \"git config advice.diverging false\"\nfatal: Not possible to fast-forward, aborting.",
+ // TestPullrequestReopen/Base_branch_deleted
+ `fatal: couldn't find remote ref base-branch`,
+ // TestPullrequestReopen/Head_branch_deleted
+ `]user2/reopen-base#1[base-branch...org26/reopen-head:head-branch]>]: branch does not exist [repo_id: 0 name: head-branch]`,
+ // TestDatabaseMissingABranch
+ `:SyncRepoBranches() [E] OpenRepository[user30/repo50]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestDatabaseMissingABranch
+ `:handlerBranchSync() [E] syncRepoBranches [50] failed: no such file or directory`,
+ // TestDatabaseMissingABranch
+ `:SyncRepoBranches() [E] OpenRepository[user30/repo51]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestDatabaseMissingABranch
+ `:handlerBranchSync() [E] syncRepoBranches [51] failed: no such file or directory`,
+ // TestDatabaseMissingABranch
+ `:SyncRepoBranches() [E] OpenRepository[user2/scoped_label]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestDatabaseMissingABranch
+ `:handlerBranchSync() [E] syncRepoBranches [55] failed: no such file or directory`,
+ // TestDatabaseMissingABranch
+ `:LoadBranches() [E] loadOneBranch() on repo #1, branch 'will-be-missing' failed: CountDivergingCommits: exit status 128 - fatal: bad revision 'master...refs/heads/will-be-missing'
+ - fatal: bad revision 'master...refs/heads/will-be-missing'`,
+ // TestDatabaseMissingABranch
+ `:SyncRepoBranches() [E] OpenRepository[user30/repo50]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestDatabaseMissingABranch
+ `:handlerBranchSync() [E] syncRepoBranches [50] failed: no such file or directory`,
+ // TestDatabaseMissingABranch
+ `:SyncRepoBranches() [E] OpenRepository[user30/repo51]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestDatabaseMissingABranch
+ `:handlerBranchSync() [E] syncRepoBranches [51] failed: no such file or directory`,
+ // TestDatabaseMissingABranch
+ `:SyncRepoBranches() [E] OpenRepository[user2/scoped_label]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestDatabaseMissingABranch
+ `:handlerBranchSync() [E] syncRepoBranches [55] failed: no such file or directory`,
+ // TestDatabaseMissingABranch
+ "LoadBranches() [E] loadOneBranch() on repo #1, branch 'will-be-missing' failed: CountDivergingCommits: exit status 128 - fatal: bad revision 'master...refs/heads/will-be-missing'\n - fatal: bad revision 'master...refs/heads/will-be-missing'",
+
+ // TestCreateNewTagProtected/Git
+ `:SSHLog() [E] ssh: Tag v-2 is protected. HookPreReceive(last) failed: internal API error response, status=403`,
+ // TestMarkDownReadmeImage
+ `:checkOutdatedBranch() [E] GetBranch: branch does not exist [repo_id: 1 name: home-md-img-check]`,
+ // TestMarkDownReadmeImage
+ `:checkOutdatedBranch() [E] GetBranch: branch does not exist [repo_id: 1 name: home-md-img-check]`,
+ // TestMarkDownReadmeImageSubfolder
+ `:checkOutdatedBranch() [E] GetBranch: branch does not exist [repo_id: 1 name: sub-home-md-img-check]`,
+ // TestMarkDownReadmeImageSubfolder
+ `:checkOutdatedBranch() [E] GetBranch: branch does not exist [repo_id: 1 name: sub-home-md-img-check]`,
+
+ // TestKeyOnlyOneType
+ `:ssh-key-test-repo-push is not authorized to write to user2/ssh-key-test-repo. ServCommand failed: internal API error response, status=401`,
+
+ // TestPullRebase
+ "/gitea-repositories/user2/repo1.git' does not appear to be a git repository\nfatal: Could not read from remote repository.\n\nPlease make sure you have the correct access rights\nand the repository exists.",
+
+ // TestPullRebaseMerge
+ "]user2/repo1#3[master...branch2]>]: branch does not exist [repo_id: 0 name: branch2]",
+
+ // TestAuthorizeNoClientID
+ `TrString() [E] Missing translation "form.ResponseType"`,
+
+ // TestWebhookForms
+ `TrString() [E] Missing translation "form.AuthorizationHeader"`,
+ `TrString() [E] Missing translation "form.Channel"`,
+ `TrString() [E] Missing translation "form.ContentType"`,
+ `TrString() [E] Missing translation "form.HTTPMethod"`,
+ `TrString() [E] Missing translation "form.PayloadURL"`,
+
+ // TestRenameInvalidUsername
+ `TrString() [E] Missing translation "form.Name"`,
+
+ // TestDatabaseCollation
+ `[E] [Error SQL Query] INSERT INTO test_collation_tbl (txt) VALUES ('main') []`,
+}
+
+func (w *testLoggerWriterCloser) recordError(msg string) {
+ for _, s := range ignoredErrorMessage {
+ if strings.Contains(msg, s) {
+ return
+ }
+ }
+
+ w.Lock()
+ defer w.Unlock()
+
+ err := w.err
+ if len(w.errs) > 0 {
+ err = w.errs[len(w.errs)-1]
+ }
+
+ if len(w.t) > 0 {
+ // format error message to easily add it to the ignore list
+ msg = fmt.Sprintf("// %s\n\t%q,", w.t[len(w.t)-1].Name(), msg)
+ }
+
+ err = errors.Join(err, errors.New(msg))
+
+ if len(w.errs) > 0 {
+ w.errs[len(w.errs)-1] = err
+ } else {
+ w.err = err
+ }
+}
+
+func (w *testLoggerWriterCloser) printMsg(msg string) {
+ // There was a data race problem: the logger system could still try to output logs after the runner is finished.
+ // So we must ensure that the "t" in stack is still valid.
+ w.RLock()
+ defer w.RUnlock()
+
+ if len(w.t) > 0 {
+ t := w.t[len(w.t)-1]
+ t.Log(msg)
+ } else {
+ // if there is no running test, the log message should be outputted to console, to avoid losing important information.
+ // the "???" prefix is used to match the "===" and "+++" in PrintCurrentTest
+ fmt.Fprintln(os.Stdout, "??? [TestLogger]", msg)
+ }
+}
+
+func (w *testLoggerWriterCloser) popT() error {
+ w.Lock()
+ defer w.Unlock()
+
+ if len(w.t) > 0 {
+ w.t = w.t[:len(w.t)-1]
+ err := w.errs[len(w.errs)-1]
+ w.errs = w.errs[:len(w.errs)-1]
+ return err
+ }
+ return w.err
+}
+
+func (w *testLoggerWriterCloser) Reset() error {
+ w.Lock()
+ if len(w.t) > 0 {
+ for _, t := range w.t {
+ if t == nil {
+ continue
+ }
+ _, _ = fmt.Fprintf(os.Stdout, "Unclosed logger writer in test: %s", t.Name())
+ t.Errorf("Unclosed logger writer in test: %s", t.Name())
+ }
+ w.t = nil
+ w.errs = nil
+ }
+ err := w.err
+ w.err = nil
+ w.Unlock()
+ return err
+}
+
+// PrintCurrentTest prints the current test to os.Stdout
+func PrintCurrentTest(t testing.TB, skip ...int) func() {
+ t.Helper()
+ start := time.Now()
+ actualSkip := 1
+ if len(skip) > 0 {
+ actualSkip = skip[0] + 1
+ }
+ _, filename, line, _ := runtime.Caller(actualSkip)
+
+ if log.CanColorStdout {
+ _, _ = fmt.Fprintf(os.Stdout, "=== %s (%s:%d)\n", fmt.Formatter(log.NewColoredValue(t.Name())), strings.TrimPrefix(filename, prefix), line)
+ } else {
+ _, _ = fmt.Fprintf(os.Stdout, "=== %s (%s:%d)\n", t.Name(), strings.TrimPrefix(filename, prefix), line)
+ }
+ WriterCloser.pushT(t)
+ return func() {
+ took := time.Since(start)
+ if took > SlowTest {
+ if log.CanColorStdout {
+ _, _ = fmt.Fprintf(os.Stdout, "+++ %s is a slow test (took %v)\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgYellow)), fmt.Formatter(log.NewColoredValue(took, log.Bold, log.FgYellow)))
+ } else {
+ _, _ = fmt.Fprintf(os.Stdout, "+++ %s is a slow test (took %v)\n", t.Name(), took)
+ }
+ }
+ timer := time.AfterFunc(SlowFlush, func() {
+ if log.CanColorStdout {
+ _, _ = fmt.Fprintf(os.Stdout, "+++ %s ... still flushing after %v ...\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgRed)), SlowFlush)
+ } else {
+ _, _ = fmt.Fprintf(os.Stdout, "+++ %s ... still flushing after %v ...\n", t.Name(), SlowFlush)
+ }
+ })
+ if err := queue.GetManager().FlushAll(context.Background(), time.Minute); err != nil {
+ t.Errorf("Flushing queues failed with error %v", err)
+ }
+ timer.Stop()
+ flushTook := time.Since(start) - took
+ if flushTook > SlowFlush {
+ if log.CanColorStdout {
+ _, _ = fmt.Fprintf(os.Stdout, "+++ %s had a slow clean-up flush (took %v)\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgRed)), fmt.Formatter(log.NewColoredValue(flushTook, log.Bold, log.FgRed)))
+ } else {
+ _, _ = fmt.Fprintf(os.Stdout, "+++ %s had a slow clean-up flush (took %v)\n", t.Name(), flushTook)
+ }
+ }
+
+ if err := WriterCloser.popT(); err != nil {
+ // disable test failure for now (too flacky)
+ _, _ = fmt.Fprintf(os.Stdout, "testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v", err)
+ // t.Errorf("testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v", err)
+ }
+ }
+}
+
+// Printf takes a format and args and prints the string to os.Stdout
+func Printf(format string, args ...any) {
+ if log.CanColorStdout {
+ for i := 0; i < len(args); i++ {
+ args[i] = log.NewColoredValue(args[i])
+ }
+ }
+ _, _ = fmt.Fprintf(os.Stdout, "\t"+format, args...)
+}
+
+// NewTestLoggerWriter creates a TestLogEventWriter as a log.LoggerProvider
+func NewTestLoggerWriter(name string, mode log.WriterMode) log.EventWriter {
+ w := &TestLogEventWriter{}
+ w.base = log.NewEventWriterBase(name, "test-log-writer", mode)
+ w.writer = WriterCloser
+ return w
+}
+
+// TestLogEventWriter is a logger which will write to the testing log
+type TestLogEventWriter struct {
+ base *log.EventWriterBaseImpl
+ writer *testLoggerWriterCloser
+}
+
+// Base implements log.EventWriter.
+func (t *TestLogEventWriter) Base() *log.EventWriterBaseImpl {
+ return t.base
+}
+
+// GetLevel implements log.EventWriter.
+func (t *TestLogEventWriter) GetLevel() log.Level {
+ return t.base.GetLevel()
+}
+
+// GetWriterName implements log.EventWriter.
+func (t *TestLogEventWriter) GetWriterName() string {
+ return t.base.GetWriterName()
+}
+
+// GetWriterType implements log.EventWriter.
+func (t *TestLogEventWriter) GetWriterType() string {
+ return t.base.GetWriterType()
+}
+
+// Run implements log.EventWriter.
+func (t *TestLogEventWriter) Run(ctx context.Context) {
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case event, ok := <-t.base.Queue:
+ if !ok {
+ return
+ }
+
+ var errorMsg string
+
+ switch msg := event.Msg.(type) {
+ case string:
+ errorMsg = msg
+ case []byte:
+ errorMsg = string(msg)
+ case io.WriterTo:
+ var buf bytes.Buffer
+ if _, err := msg.WriteTo(&buf); err != nil {
+ panic(err)
+ }
+ errorMsg = buf.String()
+ default:
+ errorMsg = fmt.Sprint(msg)
+ }
+ t.writer.Log(event.Origin.Level, errorMsg)
+ }
+ }
+}
+
+func init() {
+ const relFilePath = "modules/testlogger/testlogger.go"
+ _, filename, _, _ := runtime.Caller(0)
+ if !strings.HasSuffix(filename, relFilePath) {
+ panic("source code file path doesn't match expected: " + relFilePath)
+ }
+ prefix = strings.TrimSuffix(filename, relFilePath)
+}