Repository: gin-gonic/gin
Pull request #3957 (2024-05-07 23:54:19)

chore: support go 1.20 and set min version to go1.20
- Update the Go version requirements in `.github/workflows/gin.yml`
- Remove test files for Go versions 1.18 and 1.19
- Update the required Go version in `debug.go` and `debug_test.go`
- Rename and modify files related to Go version 1.19 and 1.20 in the `internal/bytesconv` directory

also see the comment https://github.com/gin-gonic/gin/pull/3210#issuecomment-1942950864 and https://endoflife.date/go

close https://github.com/gin-gonic/gin/pull/3693



codecov[bot]: ## [Codecov](https://app.codecov.io/gh/gin-gonic/gin/pull/3957?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) Report
Attention: Patch coverage is `0%` with `1 lines` in your changes are missing coverage. Please review.
> Project coverage is 99.18%. Comparing base [(`3dc1cd6`)](https://app.codecov.io/gh/gin-gonic/gin/commit/3dc1cd6572b4e3a0cd170a15debe546c2c72294f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) to head [(`7e7cf8c`)](https://app.codecov.io/gh/gin-gonic/gin/pull/3957?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic).
> Report is 46 commits behind head on master.

| [Files](https://app.codecov.io/gh/gin-gonic/gin/pull/3957?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | Patch % | Lines |
|---|---|---|
| [debug.go](https://app.codecov.io/gh/gin-gonic/gin/pull/3957?src=pr&el=tree&filepath=debug.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-ZGVidWcuZ28=) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/gin-gonic/gin/pull/3957?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #3957      +/-   ##
==========================================
- Coverage   99.21%   99.18%   -0.03%     
==========================================
  Files          42       43       +1     
  Lines        3182     2714     -468     
==========================================
- Hits         3157     2692     -465     
+ Misses         17       12       -5     
- Partials        8       10       +2     
```

| [Flag](https://app.codecov.io/gh/gin-gonic/gin/pull/3957/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | Coverage Δ | |
|---|---|---|
| [](https://app.codecov.io/gh/gin-gonic/gin/pull/3957/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `?` | |
| [-tags "sonic avx"](https://app.codecov.io/gh/gin-gonic/gin/pull/3957/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `99.18% <0.00%> (?)` | |
| [-tags go_json](https://app.codecov.io/gh/gin-gonic/gin/pull/3957/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `99.18% <0.00%> (?)` | |
| [-tags nomsgpack](https://app.codecov.io/gh/gin-gonic/gin/pull/3957/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `99.17% <0.00%> (?)` | |
| [go-1.18](https://app.codecov.io/gh/gin-gonic/gin/pull/3957/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `?` | |
| [go-1.19](https://app.codecov.io/gh/gin-gonic/gin/pull/3957/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `?` | |
| [go-1.20](https://app.codecov.io/gh/gin-gonic/gin/pull/3957/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `99.18% <0.00%> (-0.03%)` | :arrow_down: |
| [go-1.21](https://app.codecov.io/gh/gin-gonic/gin/pull/3957/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `99.18% <0.00%> (-0.03%)` | :arrow_down: |
| [go-1.22](https://app.codecov.io/gh/gin-gonic/gin/pull/3957/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `99.18% <0.00%> (?)` | |
| [macos-latest](https://app.codecov.io/gh/gin-gonic/gin/pull/3957/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `99.18% <0.00%> (-0.04%)` | :arrow_down: |
| [ubuntu-latest](https://app.codecov.io/gh/gin-gonic/gin/pull/3957/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `99.18% <0.00%> (-0.03%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#carryforward-flags-in-the-pull-request-comment) to find out more.


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/gin-gonic/gin/pull/3957?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic).


Patch:
diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml
index 2e43434..4a9dbc9 100644
--- a/.github/workflows/gin.yml
+++ b/.github/workflows/gin.yml
@@ -34,7 +34,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest]
-        go: ["1.18", "1.19", "1.20", "1.21", "1.22"]
+        go: ["1.20", "1.21", "1.22"]
         test-tags:
           ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"]
         include:
diff --git a/debug.go b/debug.go
index 1761fe3..ae346e9 100644
--- a/debug.go
+++ b/debug.go
@@ -10,6 +10,7 @@ import (
 	"runtime"
 	"strconv"
 	"strings"
+	"sync/atomic"
 )
 
 const ginSupportMinGoVer = 18
@@ -17,7 +18,7 @@ const ginSupportMinGoVer = 18
 // IsDebugging returns true if the framework is running in debug mode.
 // Use SetMode(gin.ReleaseMode) to disable debug mode.
 func IsDebugging() bool {
-	return ginMode == debugCode
+	return atomic.LoadInt32(&ginMode) == debugCode
 }
 
 // DebugPrintRouteFunc indicates debug log output format.
@@ -77,7 +78,7 @@ func getMinVer(v string) (uint64, error) {
 
 func debugPrintWARNINGDefault() {
 	if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer {
-		debugPrint(`[WARNING] Now Gin requires Go 1.18+.
+		debugPrint(`[WARNING] Now Gin requires Go 1.20+.
 
 `)
 	}
diff --git a/internal/bytesconv/bytesconv_1.20.go b/internal/bytesconv/bytesconv.go
similarity index 97%
rename from internal/bytesconv/bytesconv_1.20.go
rename to internal/bytesconv/bytesconv.go
index 5b6040a..a02c53c 100644
--- a/internal/bytesconv/bytesconv_1.20.go
+++ b/internal/bytesconv/bytesconv.go
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a MIT style
 // license that can be found in the LICENSE file.
 
-//go:build go1.20
-
 package bytesconv
 
 import (
diff --git a/internal/bytesconv/bytesconv_1.19.go b/internal/bytesconv/bytesconv_1.19.go
deleted file mode 100644
index 669c9c9..0000000
--- a/internal/bytesconv/bytesconv_1.19.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2020 Gin Core Team. All rights reserved.
-// Use of this source code is governed by a MIT style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.20
-
-package bytesconv
-
-import (
-	"unsafe"
-)
-
-// StringToBytes converts string to byte slice without a memory allocation.
-func StringToBytes(s string) []byte {
-	return *(*[]byte)(unsafe.Pointer(
-		&struct {
-			string
-			Cap int
-		}{s, len(s)},
-	))
-}
-
-// BytesToString converts byte slice to string without a memory allocation.
-func BytesToString(b []byte) string {
-	return *(*string)(unsafe.Pointer(&b))
-}
diff --git a/mode.go b/mode.go
index fd26d90..13aa3be 100644
--- a/mode.go
+++ b/mode.go
@@ -8,6 +8,7 @@ import (
 	"flag"
 	"io"
 	"os"
+	"sync/atomic"
 
 	"github.com/gin-gonic/gin/binding"
 )
@@ -43,10 +44,8 @@ var DefaultWriter io.Writer = os.Stdout
 // DefaultErrorWriter is the default io.Writer used by Gin to debug errors
 var DefaultErrorWriter io.Writer = os.Stderr
 
-var (
-	ginMode  = debugCode
-	modeName = DebugMode
-)
+var ginMode int32 = debugCode
+var modeName atomic.Value
 
 func init() {
 	mode := os.Getenv(EnvGinMode)
@@ -64,17 +63,16 @@ func SetMode(value string) {
 	}
 
 	switch value {
-	case DebugMode:
-		ginMode = debugCode
+	case DebugMode, "":
+		atomic.StoreInt32(&ginMode, debugCode)
 	case ReleaseMode:
-		ginMode = releaseCode
+		atomic.StoreInt32(&ginMode, releaseCode)
 	case TestMode:
-		ginMode = testCode
+		atomic.StoreInt32(&ginMode, testCode)
 	default:
 		panic("gin mode unknown: " + value + " (available mode: debug release test)")
 	}
-
-	modeName = value
+	modeName.Store(value)
 }
 
 // DisableBindValidation closes the default validator.
@@ -96,5 +94,5 @@ func EnableJsonDecoderDisallowUnknownFields() {
 
 // Mode returns current gin mode.
 func Mode() string {
-	return modeName
+	return modeName.Load().(string)
 }


Test patch:
diff --git a/context_1.18_test.go b/context_1.18_test.go
deleted file mode 100644
index 6118bea..0000000
--- a/context_1.18_test.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2021 Gin Core Team. All rights reserved.
-// Use of this source code is governed by a MIT style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.19
-
-package gin
-
-import (
-	"bytes"
-	"mime/multipart"
-	"net/http"
-	"net/http/httptest"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestContextFormFileFailed18(t *testing.T) {
-	buf := new(bytes.Buffer)
-	mw := multipart.NewWriter(buf)
-	defer func(mw *multipart.Writer) {
-		err := mw.Close()
-		if err != nil {
-			assert.Error(t, err)
-		}
-	}(mw)
-	c, _ := CreateTestContext(httptest.NewRecorder())
-	c.Request, _ = http.NewRequest("POST", "/", nil)
-	c.Request.Header.Set("Content-Type", mw.FormDataContentType())
-	c.engine.MaxMultipartMemory = 8 << 20
-	assert.Panics(t, func() {
-		f, err := c.FormFile("file")
-		assert.Error(t, err)
-		assert.Nil(t, f)
-	})
-}
diff --git a/context_1.19_test.go b/context_1.19_test.go
deleted file mode 100644
index dd75325..0000000
--- a/context_1.19_test.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2022 Gin Core Team. All rights reserved.
-// Use of this source code is governed by a MIT style
-// license that can be found in the LICENSE file.
-
-//go:build go1.19
-
-package gin
-
-import (
-	"bytes"
-	"mime/multipart"
-	"net/http"
-	"net/http/httptest"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestContextFormFileFailed19(t *testing.T) {
-	buf := new(bytes.Buffer)
-	mw := multipart.NewWriter(buf)
-	mw.Close()
-	c, _ := CreateTestContext(httptest.NewRecorder())
-	c.Request, _ = http.NewRequest("POST", "/", nil)
-	c.Request.Header.Set("Content-Type", mw.FormDataContentType())
-	c.engine.MaxMultipartMemory = 8 << 20
-	f, err := c.FormFile("file")
-	assert.Error(t, err)
-	assert.Nil(t, f)
-}
diff --git a/context_test.go b/context_test.go
index e9bbae5..ae34c65 100644
--- a/context_test.go
+++ b/context_test.go
@@ -90,6 +90,19 @@ func TestContextFormFile(t *testing.T) {
 	assert.NoError(t, c.SaveUploadedFile(f, "test"))
 }
 
+func TestContextFormFileFailed(t *testing.T) {
+	buf := new(bytes.Buffer)
+	mw := multipart.NewWriter(buf)
+	mw.Close()
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	c.Request, _ = http.NewRequest("POST", "/", nil)
+	c.Request.Header.Set("Content-Type", mw.FormDataContentType())
+	c.engine.MaxMultipartMemory = 8 << 20
+	f, err := c.FormFile("file")
+	assert.Error(t, err)
+	assert.Nil(t, f)
+}
+
 func TestContextMultipartForm(t *testing.T) {
 	buf := new(bytes.Buffer)
 	mw := multipart.NewWriter(buf)
diff --git a/debug_test.go b/debug_test.go
index 2d5e9a5..e390972 100644
--- a/debug_test.go
+++ b/debug_test.go
@@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
 	})
 	m, e := getMinVer(runtime.Version())
 	if e == nil && m < ginSupportMinGoVer {
-		assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.18+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
+		assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.20+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
 	} else {
 		assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
 	}
diff --git a/mode_test.go b/mode_test.go
index 2407f46..be03a9d 100644
--- a/mode_test.go
+++ b/mode_test.go
@@ -5,8 +5,8 @@
 package gin
 
 import (
-	"flag"
 	"os"
+	"sync/atomic"
 	"testing"
 
 	"github.com/gin-gonic/gin/binding"
@@ -18,31 +18,24 @@ func init() {
 }
 
 func TestSetMode(t *testing.T) {
-	assert.Equal(t, testCode, ginMode)
+	assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode))
 	assert.Equal(t, TestMode, Mode())
 	os.Unsetenv(EnvGinMode)
 
 	SetMode("")
-	assert.Equal(t, testCode, ginMode)
+	assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode))
 	assert.Equal(t, TestMode, Mode())
 
-	tmp := flag.CommandLine
-	flag.CommandLine = flag.NewFlagSet("", flag.ContinueOnError)
-	SetMode("")
-	assert.Equal(t, debugCode, ginMode)
-	assert.Equal(t, DebugMode, Mode())
-	flag.CommandLine = tmp
-
 	SetMode(DebugMode)
-	assert.Equal(t, debugCode, ginMode)
+	assert.Equal(t, int32(debugCode), atomic.LoadInt32(&ginMode))
 	assert.Equal(t, DebugMode, Mode())
 
 	SetMode(ReleaseMode)
-	assert.Equal(t, releaseCode, ginMode)
+	assert.Equal(t, int32(releaseCode), atomic.LoadInt32(&ginMode))
 	assert.Equal(t, ReleaseMode, Mode())
 
 	SetMode(TestMode)
-	assert.Equal(t, testCode, ginMode)
+	assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode))
 	assert.Equal(t, TestMode, Mode())
 
 	assert.Panics(t, func() { SetMode("unknown") })