internal/http3: fix build of tests with GOEXPERIMENT=nosynctest

The tests in qpack_decode_test.go require synctest helpers from
http3_test.go, but that file has a goexperiment.synctest build
constraint.

To make builds work when GOEXPERIMENT=nosynctest is specified the
synctest helpers are refactored into http3_synctest_test.go (with the
same build constraint) and the non-synctest related functionality is
kept in http3_test.go.

Change-Id: Iae339dc1895f27e7ac5ba985e204f4868c229a4d
Reviewed-on: https://go-review.googlesource.com/c/net/+/660535
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/internal/http3/http3_synctest_test.go b/internal/http3/http3_synctest_test.go
new file mode 100644
index 0000000..ad26c6d
--- /dev/null
+++ b/internal/http3/http3_synctest_test.go
@@ -0,0 +1,48 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24 && goexperiment.synctest
+
+package http3
+
+import (
+	"slices"
+	"testing"
+	"testing/synctest"
+)
+
+// runSynctest runs f in a synctest.Run bubble.
+// It arranges for t.Cleanup functions to run within the bubble.
+func runSynctest(t *testing.T, f func(t testing.TB)) {
+	synctest.Run(func() {
+		ct := &cleanupT{T: t}
+		defer ct.done()
+		f(ct)
+	})
+}
+
+// runSynctestSubtest runs f in a subtest in a synctest.Run bubble.
+func runSynctestSubtest(t *testing.T, name string, f func(t testing.TB)) {
+	t.Run(name, func(t *testing.T) {
+		runSynctest(t, f)
+	})
+}
+
+// cleanupT wraps a testing.T and adds its own Cleanup method.
+// Used to execute cleanup functions within a synctest bubble.
+type cleanupT struct {
+	*testing.T
+	cleanups []func()
+}
+
+// Cleanup replaces T.Cleanup.
+func (t *cleanupT) Cleanup(f func()) {
+	t.cleanups = append(t.cleanups, f)
+}
+
+func (t *cleanupT) done() {
+	for _, f := range slices.Backward(t.cleanups) {
+		f()
+	}
+}
diff --git a/internal/http3/http3_test.go b/internal/http3/http3_test.go
index f490ad3..f6fb2e9 100644
--- a/internal/http3/http3_test.go
+++ b/internal/http3/http3_test.go
@@ -2,17 +2,14 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.24 && goexperiment.synctest
+//go:build go1.24
 
 package http3
 
 import (
 	"encoding/hex"
 	"os"
-	"slices"
 	"strings"
-	"testing"
-	"testing/synctest"
 )
 
 func init() {
@@ -25,41 +22,6 @@
 	os.Setenv("GODEBUG", os.Getenv("GODEBUG")+",asynctimerchan=0")
 }
 
-// runSynctest runs f in a synctest.Run bubble.
-// It arranges for t.Cleanup functions to run within the bubble.
-func runSynctest(t *testing.T, f func(t testing.TB)) {
-	synctest.Run(func() {
-		ct := &cleanupT{T: t}
-		defer ct.done()
-		f(ct)
-	})
-}
-
-// runSynctestSubtest runs f in a subtest in a synctest.Run bubble.
-func runSynctestSubtest(t *testing.T, name string, f func(t testing.TB)) {
-	t.Run(name, func(t *testing.T) {
-		runSynctest(t, f)
-	})
-}
-
-// cleanupT wraps a testing.T and adds its own Cleanup method.
-// Used to execute cleanup functions within a synctest bubble.
-type cleanupT struct {
-	*testing.T
-	cleanups []func()
-}
-
-// Cleanup replaces T.Cleanup.
-func (t *cleanupT) Cleanup(f func()) {
-	t.cleanups = append(t.cleanups, f)
-}
-
-func (t *cleanupT) done() {
-	for _, f := range slices.Backward(t.cleanups) {
-		f()
-	}
-}
-
 func unhex(s string) []byte {
 	b, err := hex.DecodeString(strings.Map(func(c rune) rune {
 		switch c {
diff --git a/internal/http3/qpack_decode_test.go b/internal/http3/qpack_decode_test.go
index 1b779aa..3b9a995 100644
--- a/internal/http3/qpack_decode_test.go
+++ b/internal/http3/qpack_decode_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.24
+//go:build go1.24 && goexperiment.synctest
 
 package http3