feat: detect incorrect integration test functions (#8352)
- I have seen multiple times where a test function tries to prepare the
testing environment more than once, this can lead to bugs and false
positives of testing code. I would attribute this to lack of
documentation on how to write integration tests.
- To detect such cases, keep track when we are in a prepared test
environment and fail when some testing code is tries to once again
prepare the test environment.
- The message is logged to the function call that is requesting to
prepare the test environment, for example: `change_default_branch_test.go:19: Cannot prepare a test environment if you are already in a test environment. This is a bug in your testing code.`
A example of what this will be able to catch, 6226f464cec870991302c62a514d11ddb2066b69:
```go
func TestFoo(t *testing.T) {
	defer PrepareTestEnv(t)()
	t.Run("Bar", func(t *testing.T) {
		defer PrepareTestEnv(t)() // Should very likely be PrintCurrentTest
	})
}
```
```go
func TestBar(t *testing.T) {
	onGiteaRun(t, func(t *testing.T, _ *url.URL) {
		defer PrepareTestEnv(t)() // Already called by onGiteaRun.
	})
}
```
```go
func TestFooBar(t *testing.T) {
	defer PrepareTestEnv(t)() // This will be called by onGiteaRun later on and very unlikely to do this before the call to onGiteaRun.
	onGiteaRun(t, func(t *testing.T, _ *url.URL) {
		// [...]
	})
}
```
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8352
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-committed-by: Gusted <postmaster@gusted.xyz>
	
	
This commit is contained in:
		
					parent
					
						
							
								c57dea336c
							
						
					
				
			
			
				commit
				
					
						4927d4ee3d
					
				
			
		
					 7 changed files with 47 additions and 32 deletions
				
			
		| 
						 | 
				
			
			@ -17,36 +17,40 @@ import (
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
func TestAPIGetRawFileOrLFS(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	// Test with raw file
 | 
			
		||||
	req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/media/README.md")
 | 
			
		||||
	resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String())
 | 
			
		||||
 | 
			
		||||
	// Test with LFS
 | 
			
		||||
	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | 
			
		||||
		httpContext := NewAPITestContext(t, "user2", "repo-lfs-test", auth_model.AccessTokenScopeWriteRepository)
 | 
			
		||||
		doAPICreateRepository(httpContext, nil, git.Sha1ObjectFormat, func(t *testing.T, repository api.Repository) { // FIXME: use forEachObjectFormat
 | 
			
		||||
			u.Path = httpContext.GitPath()
 | 
			
		||||
			dstPath := t.TempDir()
 | 
			
		||||
		t.Run("Normal raw file", func(t *testing.T) {
 | 
			
		||||
			defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
			u.Path = httpContext.GitPath()
 | 
			
		||||
			u.User = url.UserPassword("user2", userPassword)
 | 
			
		||||
			req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/media/README.md")
 | 
			
		||||
			resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
			assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String())
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
			t.Run("Clone", doGitClone(dstPath, u))
 | 
			
		||||
		t.Run("LFS raw file", func(t *testing.T) {
 | 
			
		||||
			defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
			dstPath2 := t.TempDir()
 | 
			
		||||
			httpContext := NewAPITestContext(t, "user2", "repo-lfs-test", auth_model.AccessTokenScopeWriteRepository)
 | 
			
		||||
			doAPICreateRepository(httpContext, nil, git.Sha1ObjectFormat, func(t *testing.T, repository api.Repository) { // FIXME: use forEachObjectFormat
 | 
			
		||||
				u.Path = httpContext.GitPath()
 | 
			
		||||
				dstPath := t.TempDir()
 | 
			
		||||
 | 
			
		||||
			t.Run("Partial Clone", doPartialGitClone(dstPath2, u))
 | 
			
		||||
				u.Path = httpContext.GitPath()
 | 
			
		||||
				u.User = url.UserPassword("user2", userPassword)
 | 
			
		||||
 | 
			
		||||
			lfs, _ := lfsCommitAndPushTest(t, dstPath)
 | 
			
		||||
				t.Run("Clone", doGitClone(dstPath, u))
 | 
			
		||||
 | 
			
		||||
			reqLFS := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/media/"+lfs)
 | 
			
		||||
			respLFS := MakeRequestNilResponseRecorder(t, reqLFS, http.StatusOK)
 | 
			
		||||
			assert.Equal(t, littleSize, respLFS.Length)
 | 
			
		||||
				dstPath2 := t.TempDir()
 | 
			
		||||
 | 
			
		||||
			doAPIDeleteRepository(httpContext)
 | 
			
		||||
				t.Run("Partial Clone", doPartialGitClone(dstPath2, u))
 | 
			
		||||
 | 
			
		||||
				lfs, _ := lfsCommitAndPushTest(t, dstPath)
 | 
			
		||||
 | 
			
		||||
				reqLFS := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/media/"+lfs)
 | 
			
		||||
				respLFS := MakeRequestNilResponseRecorder(t, reqLFS, http.StatusOK)
 | 
			
		||||
				assert.Equal(t, littleSize, respLFS.Length)
 | 
			
		||||
 | 
			
		||||
				doAPIDeleteRepository(httpContext)
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1350,8 +1350,6 @@ body:
 | 
			
		|||
 | 
			
		||||
func TestIssueUnsubscription(t *testing.T) {
 | 
			
		||||
	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | 
			
		||||
		defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 | 
			
		||||
		repo, _, f := tests.CreateDeclarativeRepoWithOptions(t, user, tests.DeclarativeRepoOptions{
 | 
			
		||||
			AutoInit: optional.Some(false),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,7 +43,6 @@ func TestMirrorPush(t *testing.T) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func testMirrorPush(t *testing.T, u *url.URL) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
	defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, true)()
 | 
			
		||||
 | 
			
		||||
	require.NoError(t, migrations.Init())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -287,8 +287,6 @@ func testDeleteRepository(t *testing.T, session *TestSession, ownerName, repoNam
 | 
			
		|||
 | 
			
		||||
func TestPullBranchDelete(t *testing.T) {
 | 
			
		||||
	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | 
			
		||||
		defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
		session := loginUser(t, "user1")
 | 
			
		||||
		testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
 | 
			
		||||
		testCreateBranch(t, session, "user1", "repo1", "branch/master", "master1", http.StatusSeeOther)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -89,7 +89,6 @@ func TestAPIPullUpdateByRebase(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
func TestAPIViewUpdateSettings(t *testing.T) {
 | 
			
		||||
	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
 | 
			
		||||
		defer tests.PrepareTestEnv(t)()
 | 
			
		||||
		// Create PR to test
 | 
			
		||||
		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 | 
			
		||||
		org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26})
 | 
			
		||||
| 
						 | 
				
			
			@ -136,7 +135,6 @@ func TestViewPullUpdateByRebase(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
func testViewPullUpdate(t *testing.T, updateStyle string) {
 | 
			
		||||
	defer test.MockVariableValue(&setting.Repository.PullRequest.DefaultUpdateStyle, updateStyle)()
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
	// Create PR to test
 | 
			
		||||
	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 | 
			
		||||
	org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -198,9 +198,9 @@ func TestViewRepoWithSymlinks(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
// TestViewAsRepoAdmin tests PR #2167
 | 
			
		||||
func TestViewAsRepoAdmin(t *testing.T) {
 | 
			
		||||
	for _, user := range []string{"user2", "user4"} {
 | 
			
		||||
		defer tests.PrepareTestEnv(t)()
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	for _, user := range []string{"user2", "user4"} {
 | 
			
		||||
		session := loginUser(t, user)
 | 
			
		||||
 | 
			
		||||
		req := NewRequest(t, "GET", "/user2/repo1.git")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,6 +13,7 @@ import (
 | 
			
		|||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync/atomic"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -312,9 +313,26 @@ func PrepareCleanPackageData(t testing.TB) {
 | 
			
		|||
	require.NoError(t, storage.Clean(storage.Packages))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// inTestEnv keeps track if we are current inside a test environment, this is
 | 
			
		||||
// used to detect if testing code tries to prepare a test environment more than
 | 
			
		||||
// once.
 | 
			
		||||
var inTestEnv atomic.Bool
 | 
			
		||||
 | 
			
		||||
func PrepareTestEnv(t testing.TB, skip ...int) func() {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
	deferFn := PrintCurrentTest(t, util.OptionalArg(skip)+1)
 | 
			
		||||
 | 
			
		||||
	if !inTestEnv.CompareAndSwap(false, true) {
 | 
			
		||||
		t.Fatal("Cannot prepare a test environment if you are already in a test environment. This is a bug in your testing code.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	deferPrintCurrentTest := PrintCurrentTest(t, util.OptionalArg(skip)+1)
 | 
			
		||||
	deferFn := func() {
 | 
			
		||||
		deferPrintCurrentTest()
 | 
			
		||||
 | 
			
		||||
		if !inTestEnv.CompareAndSwap(true, false) {
 | 
			
		||||
			t.Fatal("Tried to leave test environment, but we are no longer in a test environment. This should not happen.")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cancelProcesses(t, 30*time.Second)
 | 
			
		||||
	t.Cleanup(func() { cancelProcesses(t, 0) }) // cancel remaining processes in a non-blocking way
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue