nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: zeromicro/go-zero
Pull request #3944 (2024-02-29 08:29:50)

feat(redis): added and impl `ZADDNX` command
added `Zaddnx`, `ZaddnxCtx`, `ZaddnxFloat` and `ZaddnxFloatCtx` to impl `ZADDNX` command.

help to review it, plz ~~~

@kevwan 


Patch:
diff --git a/core/stores/redis/redis.go b/core/stores/redis/redis.go
index 40fb008c..96881115 100644
--- a/core/stores/redis/redis.go
+++ b/core/stores/redis/redis.go
@@ -2074,6 +2074,45 @@ func (s *Redis) ZaddFloatCtx(ctx context.Context, key string, score float64, val
 	return
 }
 
+// Zaddnx is the implementation of redis zadd nx command.
+func (s *Redis) Zaddnx(key string, score int64, value string) (val bool, err error) {
+	return s.ZaddnxCtx(context.Background(), key, score, value)
+}
+
+// ZaddnxCtx is the implementation of redis zadd nx command.
+func (s *Redis) ZaddnxCtx(ctx context.Context, key string, score int64, value string) (val bool, err error) {
+	return s.ZaddnxFloatCtx(ctx, key, float64(score), value)
+}
+
+// ZaddnxFloat is the implementation of redis zaddnx command.
+func (s *Redis) ZaddnxFloat(key string, score float64, value string) (bool, error) {
+	return s.ZaddFloatCtx(context.Background(), key, score, value)
+}
+
+// ZaddnxFloatCtx is the implementation of redis zaddnx command.
+func (s *Redis) ZaddnxFloatCtx(ctx context.Context, key string, score float64, value string) (
+	val bool, err error) {
+	err = s.brk.DoWithAcceptable(func() error {
+		conn, err := getRedis(s)
+		if err != nil {
+			return err
+		}
+
+		v, err := conn.ZAddNX(ctx, key, red.Z{
+			Score:  score,
+			Member: value,
+		}).Result()
+		if err != nil {
+			return err
+		}
+
+		val = v == 1
+		return nil
+	}, acceptable)
+
+	return
+}
+
 // Zadds is the implementation of redis zadds command.
 func (s *Redis) Zadds(key string, ps ...Pair) (int64, error) {
 	return s.ZaddsCtx(context.Background(), key, ps...)


Test patch:
diff --git a/core/stores/redis/redis_test.go b/core/stores/redis/redis_test.go
index b455523c..1515d1cb 100644
--- a/core/stores/redis/redis_test.go
+++ b/core/stores/redis/redis_test.go
@@ -1584,6 +1584,24 @@ func TestRedis_SortedSetByFloat64(t *testing.T) {
 	})
 }
 
+func TestRedis_Zaddnx(t *testing.T) {
+	runOnRedis(t, func(client *Redis) {
+		ok, err := client.Zadd("key", 1, "value1")
+		assert.Nil(t, err)
+		assert.True(t, ok)
+		ok, err = client.Zaddnx("key", 2, "value1")
+		assert.Nil(t, err)
+		assert.False(t, ok)
+
+		ok, err = client.ZaddFloat("key", 1.1, "value2")
+		assert.Nil(t, err)
+		assert.True(t, ok)
+		ok, err = client.ZaddnxFloat("key", 1.1, "value3")
+		assert.Nil(t, err)
+		assert.True(t, ok)
+	})
+}
+
 func TestRedis_IncrbyFloat(t *testing.T) {
 	runOnRedis(t, func(client *Redis) {
 		incrVal, err := client.IncrbyFloat("key", 0.002)
Repository: zeromicro/go-zero
Pull request #4026 (2024-03-28 03:40:44)

fix(httpx): support array field for request dto
I would like to fix the array parsing issue
could you please to help check

https://github.com/zeromicro/go-zero/issues/4025

![image](https://github.com/zeromicro/go-zero/assets/5271457/34f9a7e1-55cc-486e-ab21-fe6c2bb07d55)

![image](https://github.com/zeromicro/go-zero/assets/5271457/997fac6a-1825-44c3-beb8-df892a05f819)


kevwan: @shyandsy Thanks for your PR! Please fix test failure.

Patch:
diff --git a/rest/httpx/util.go b/rest/httpx/util.go
index 9623761a..9301522c 100644
--- a/rest/httpx/util.go
+++ b/rest/httpx/util.go
@@ -21,9 +21,10 @@ func GetFormValues(r *http.Request) (map[string]any, error) {
 
 	params := make(map[string]any, len(r.Form))
 	for name := range r.Form {
-		formValue := r.Form.Get(name)
-		if len(formValue) > 0 {
-			params[name] = formValue
+		if len(r.Form[name]) > 1 {
+			params[name] = r.Form[name]
+		} else if len(r.Form[name]) == 1 {
+			params[name] = r.Form.Get(name)
 		}
 	}
 


Test patch:
diff --git a/rest/httpx/requests_test.go b/rest/httpx/requests_test.go
index a1d3f6a8..3c4d8d16 100644
--- a/rest/httpx/requests_test.go
+++ b/rest/httpx/requests_test.go
@@ -16,17 +16,21 @@ import (
 
 func TestParseForm(t *testing.T) {
 	var v struct {
-		Name    string  `form:"name"`
-		Age     int     `form:"age"`
-		Percent float64 `form:"percent,optional"`
+		Name     string   `form:"name"`
+		Age      int      `form:"age"`
+		Percent  float64  `form:"percent,optional"`
+		Statuses []string `form:"statuses[],optional"`
 	}
 
-	r, err := http.NewRequest(http.MethodGet, "/a?name=hello&age=18&percent=3.4", http.NoBody)
+	r, err := http.NewRequest(http.MethodGet, "/a?name=hello&age=18&percent=3.4&statuses[]=try&statuses[]=done", http.NoBody)
 	assert.Nil(t, err)
 	assert.Nil(t, Parse(r, &v))
 	assert.Equal(t, "hello", v.Name)
 	assert.Equal(t, 18, v.Age)
 	assert.Equal(t, 3.4, v.Percent)
+	assert.Equal(t, 2, len(v.Statuses))
+	assert.True(t, v.Statuses[0] == "try" || v.Statuses[1] == "try")
+	assert.True(t, v.Statuses[0] == "done" || v.Statuses[1] == "done")
 }
 
 func TestParseForm_Error(t *testing.T) {
Repository: zeromicro/go-zero
Pull request #4040 (2024-04-04 12:18:28)

fix: bug on form data with slices


Patch:
diff --git a/rest/httpx/util.go b/rest/httpx/util.go
index 48d76592..9623761a 100644
--- a/rest/httpx/util.go
+++ b/rest/httpx/util.go
@@ -21,15 +21,9 @@ func GetFormValues(r *http.Request) (map[string]any, error) {
 
 	params := make(map[string]any, len(r.Form))
 	for name := range r.Form {
-		switch len(r.Form[name]) {
-		case 1:
-			formValue := r.Form.Get(name)
-			if len(formValue) > 0 {
-				params[name] = formValue
-			}
-		default:
-			// len(r.Form[name]) > 1, never be 0
-			params[name] = r.Form[name]
+		formValue := r.Form.Get(name)
+		if len(formValue) > 0 {
+			params[name] = formValue
 		}
 	}
 


Test patch:
diff --git a/rest/httpx/requests_test.go b/rest/httpx/requests_test.go
index 157b1dbb..1a1c8a10 100644
--- a/rest/httpx/requests_test.go
+++ b/rest/httpx/requests_test.go
@@ -15,25 +15,37 @@ import (
 )
 
 func TestParseForm(t *testing.T) {
-	var v struct {
-		Name     string   `form:"name"`
-		Age      int      `form:"age"`
-		Percent  float64  `form:"percent,optional"`
-		Statuses []string `form:"statuses,optional"`
-		NoValue  string   `form:"noValue,optional"`
-	}
+	t.Run("slice", func(t *testing.T) {
+		var v struct {
+			Name    string  `form:"name"`
+			Age     int     `form:"age"`
+			Percent float64 `form:"percent,optional"`
+		}
 
-	r, err := http.NewRequest(
-		http.MethodGet,
-		"/a?name=hello&age=18&percent=3.4&statuses=try&statuses=done",
-		http.NoBody)
-	assert.Nil(t, err)
-	assert.Nil(t, Parse(r, &v))
-	assert.Equal(t, "hello", v.Name)
-	assert.Equal(t, 18, v.Age)
-	assert.Equal(t, 3.4, v.Percent)
-	assert.EqualValues(t, []string{"try", "done"}, v.Statuses)
-	assert.Equal(t, 0, len(v.NoValue))
+		r, err := http.NewRequest(
+			http.MethodGet,
+			"/a?name=hello&age=18&percent=3.4",
+			http.NoBody)
+		assert.Nil(t, err)
+		assert.Nil(t, Parse(r, &v))
+		assert.Equal(t, "hello", v.Name)
+		assert.Equal(t, 18, v.Age)
+		assert.Equal(t, 3.4, v.Percent)
+	})
+
+	t.Run("no value", func(t *testing.T) {
+		var v struct {
+			NoValue string `form:"noValue,optional"`
+		}
+
+		r, err := http.NewRequest(
+			http.MethodGet,
+			"/a?name=hello&age=18&percent=3.4&statuses=try&statuses=done&singleValue=one",
+			http.NoBody)
+		assert.Nil(t, err)
+		assert.Nil(t, Parse(r, &v))
+		assert.Equal(t, 0, len(v.NoValue))
+	})
 }
 
 func TestParseForm_Error(t *testing.T) {
Repository: zeromicro/go-zero
Pull request #2801 (2023-01-17 13:26:10)

chore: add more tests


Patch:
diff --git a/core/lang/lang.go b/core/lang/lang.go
index 3e2932fd..6ca50973 100644
--- a/core/lang/lang.go
+++ b/core/lang/lang.go
@@ -29,7 +29,7 @@ func Repr(v interface{}) string {
 	}
 
 	val := reflect.ValueOf(v)
-	if val.Kind() == reflect.Ptr && !val.IsNil() {
+	for val.Kind() == reflect.Ptr && !val.IsNil() {
 		val = val.Elem()
 	}
 


Test patch:
diff --git a/core/lang/lang_test.go b/core/lang/lang_test.go
index 1527defd..2c28a5e0 100644
--- a/core/lang/lang_test.go
+++ b/core/lang/lang_test.go
@@ -1,6 +1,9 @@
 package lang
 
 import (
+	"encoding/json"
+	"errors"
+	"reflect"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -110,6 +113,28 @@ func TestRepr(t *testing.T) {
 	}
 }
 
+func TestReprOfValue(t *testing.T) {
+	t.Run("error", func(t *testing.T) {
+		assert.Equal(t, "error", reprOfValue(reflect.ValueOf(errors.New("error"))))
+	})
+
+	t.Run("stringer", func(t *testing.T) {
+		assert.Equal(t, "1.23", reprOfValue(reflect.ValueOf(json.Number("1.23"))))
+	})
+
+	t.Run("int", func(t *testing.T) {
+		assert.Equal(t, "1", reprOfValue(reflect.ValueOf(1)))
+	})
+
+	t.Run("int", func(t *testing.T) {
+		assert.Equal(t, "1", reprOfValue(reflect.ValueOf("1")))
+	})
+
+	t.Run("int", func(t *testing.T) {
+		assert.Equal(t, "1", reprOfValue(reflect.ValueOf(uint(1))))
+	})
+}
+
 type mockStringable struct{}
 
 func (m mockStringable) String() string {
Repository: zeromicro/go-zero
Pull request #2828 (2023-01-29 10:13:04)

chore: improve codecov

codecov[bot]: # [Codecov](https://codecov.io/gh/zeromicro/go-zero/pull/2828?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#2828](https://codecov.io/gh/zeromicro/go-zero/pull/2828?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (0564222) into [master](https://codecov.io/gh/zeromicro/go-zero/commit/fde05ccb2849ac7ff742c82446bcb369c99a2cef?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (fde05cc) will **increase** coverage by `0.03%`.
> The diff coverage is `n/a`.


| [Impacted Files](https://codecov.io/gh/zeromicro/go-zero/pull/2828?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [core/stores/redis/conf.go](https://codecov.io/gh/zeromicro/go-zero/pull/2828?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-Y29yZS9zdG9yZXMvcmVkaXMvY29uZi5nbw==) | `100.00% <ø> (ø)` | |
| [core/stores/redis/redis.go](https://codecov.io/gh/zeromicro/go-zero/pull/2828?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-Y29yZS9zdG9yZXMvcmVkaXMvcmVkaXMuZ28=) | `98.85% <ø> (+0.19%)` | :arrow_up: |
| [core/logx/rotatelogger.go](https://codecov.io/gh/zeromicro/go-zero/pull/2828?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-Y29yZS9sb2d4L3JvdGF0ZWxvZ2dlci5nbw==) | `73.80% <0.00%> (+1.10%)` | :arrow_up: |


Patch:
diff --git a/core/stores/redis/conf.go b/core/stores/redis/conf.go
index 711ce817..00a7ff3d 100644
--- a/core/stores/redis/conf.go
+++ b/core/stores/redis/conf.go
@@ -29,6 +29,7 @@ type (
 	}
 )
 
+// NewRedis returns a Redis.
 // Deprecated: use MustNewRedis or NewRedis instead.
 func (rc RedisConf) NewRedis() *Redis {
 	var opts []Option
diff --git a/core/stores/redis/redis.go b/core/stores/redis/redis.go
index 9572aeed..7ee06ea9 100644
--- a/core/stores/redis/redis.go
+++ b/core/stores/redis/redis.go
@@ -86,6 +86,7 @@ type (
 	StringCmd = red.StringCmd
 )
 
+// New returns a Redis with given options.
 // Deprecated: use MustNewRedis or NewRedis instead.
 func New(addr string, opts ...Option) *Redis {
 	return newRedis(addr, opts...)


Test patch:
diff --git a/core/stores/redis/redis_test.go b/core/stores/redis/redis_test.go
index ec36e02b..7a83b6f4 100644
--- a/core/stores/redis/redis_test.go
+++ b/core/stores/redis/redis_test.go
@@ -1776,7 +1776,10 @@ func runOnRedis(t *testing.T, fn func(client *Redis)) {
 		}
 	}()
 
-	fn(New(s.Addr()))
+	fn(MustNewRedis(RedisConf{
+		Host: s.Addr(),
+		Type: NodeType,
+	}))
 }
 
 func runOnRedisWithError(t *testing.T, fn func(client *Redis)) {
Repository: zeromicro/go-zero
Pull request #2931 (2023-02-27 06:07:55)

interface conversion: *handler.timeoutWriter is not http.Hijacker: missing method Hijack
fix

interface conversion: *handler.timeoutWriter is not http.Hijacker: missing method Hijack

yangjinheng: similar problems：

https://github.com/zeromicro/go-zero/pull/2028
kevwan: Please make the test pass.
yangjinheng: > Please make the test pass.

ok ，please review

Patch:
diff --git a/rest/handler/timeouthandler.go b/rest/handler/timeouthandler.go
index f155c062..41fe38f2 100644
--- a/rest/handler/timeouthandler.go
+++ b/rest/handler/timeouthandler.go
@@ -1,11 +1,13 @@
 package handler
 
 import (
+	"bufio"
 	"bytes"
 	"context"
 	"errors"
 	"fmt"
 	"io"
+	"net"
 	"net/http"
 	"path"
 	"runtime"
@@ -125,6 +127,14 @@ type timeoutWriter struct {
 
 var _ http.Pusher = (*timeoutWriter)(nil)
 
+func (tw *timeoutWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+	if hijacked, ok := tw.w.(http.Hijacker); ok {
+		return hijacked.Hijack()
+	}
+
+	return nil, nil, errors.New("server doesn't support hijacking")
+}
+
 // Header returns the underline temporary http.Header.
 func (tw *timeoutWriter) Header() http.Header { return tw.h }
 


Test patch:
diff --git a/rest/handler/timeouthandler_test.go b/rest/handler/timeouthandler_test.go
index 73e75cce..23eb88c6 100644
--- a/rest/handler/timeouthandler_test.go
+++ b/rest/handler/timeouthandler_test.go
@@ -10,6 +10,7 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/zeromicro/go-zero/rest/internal/response"
 )
 
 func init() {
@@ -134,6 +135,30 @@ func TestTimeoutClientClosed(t *testing.T) {
 	assert.Equal(t, statusClientClosedRequest, resp.Code)
 }
 
+func TestTimeoutHijack(t *testing.T) {
+	resp := httptest.NewRecorder()
+
+	writer := &timeoutWriter{
+		w: &response.WithCodeResponseWriter{
+			Writer: resp,
+		},
+	}
+
+	assert.NotPanics(t, func() {
+		writer.Hijack()
+	})
+
+	writer = &timeoutWriter{
+		w: &response.WithCodeResponseWriter{
+			Writer: mockedHijackable{resp},
+		},
+	}
+
+	assert.NotPanics(t, func() {
+		writer.Hijack()
+	})
+}
+
 func TestTimeoutPusher(t *testing.T) {
 	handler := &timeoutWriter{
 		w: mockedPusher{},
Repository: zeromicro/go-zero
Pull request #3013 (2023-03-10 15:03:01)

fix: avoid unmarshal panic with incorrect map keys #3002

codecov[bot]: ## [Codecov](https://codecov.io/gh/zeromicro/go-zero/pull/3013?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#3013](https://codecov.io/gh/zeromicro/go-zero/pull/3013?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (43b8b62) into [master](https://codecov.io/gh/zeromicro/go-zero/commit/123c61ad128e6df831cf5cac16f5e524ad01fff2?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (123c61a) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

> :exclamation: Current head 43b8b62 differs from pull request most recent head f16012f. Consider uploading reports for the commit f16012f to get more accurate results


| [Impacted Files](https://codecov.io/gh/zeromicro/go-zero/pull/3013?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [core/mapping/unmarshaler.go](https://codecov.io/gh/zeromicro/go-zero/pull/3013?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-Y29yZS9tYXBwaW5nL3VubWFyc2hhbGVyLmdv) | `89.22% <100.00%> (+0.04%)` | :arrow_up: |


Patch:
diff --git a/core/mapping/unmarshaler.go b/core/mapping/unmarshaler.go
index f8674211..d19469f3 100644
--- a/core/mapping/unmarshaler.go
+++ b/core/mapping/unmarshaler.go
@@ -289,6 +289,10 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any)
 		return reflect.ValueOf(mapValue), nil
 	}
 
+	if keyType != valueType.Key() {
+		return emptyValue, errTypeMismatch
+	}
+
 	refValue := reflect.ValueOf(mapValue)
 	targetValue := reflect.MakeMapWithSize(mapType, refValue.Len())
 	dereffedElemType := Deref(elemType)


Test patch:
diff --git a/core/mapping/unmarshaler_test.go b/core/mapping/unmarshaler_test.go
index cdd7b98a..4b6148a2 100644
--- a/core/mapping/unmarshaler_test.go
+++ b/core/mapping/unmarshaler_test.go
@@ -4437,3 +4437,18 @@ func TestFillDefaultUnmarshal(t *testing.T) {
 		assert.Error(t, err)
 	})
 }
+
+func Test_UnmarshalMap(t *testing.T) {
+	type Customer struct {
+		Names map[int]string `key:"names"`
+	}
+
+	input := map[string]any{
+		"names": map[string]any{
+			"19": "Tom",
+		},
+	}
+
+	var customer Customer
+	assert.ErrorIs(t, UnmarshalKey(input, &customer), errTypeMismatch)
+}
Repository: zeromicro/go-zero
Pull request #3177 (2023-04-27 03:22:27)

no \t to space line
![image](https://user-images.githubusercontent.com/28895116/234751366-c7053655-d318-42a2-b027-5d1d864fa382.png)

before this, there will be meaningless tabs between multiple request definitions.

codecov[bot]: ## [Codecov](https://codecov.io/gh/zeromicro/go-zero/pull/3177?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#3177](https://codecov.io/gh/zeromicro/go-zero/pull/3177?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (08fc6f4) into [master](https://codecov.io/gh/zeromicro/go-zero/commit/ba9d510cdb8c41a31bbac74b0ad3bdf044e144ed?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (ba9d510) will **not change** coverage.
> The diff coverage is `n/a`.


[see 2 files with indirect coverage changes](https://codecov.io/gh/zeromicro/go-zero/pull/3177/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)


Patch:
diff --git a/tools/goctl/api/format/format.go b/tools/goctl/api/format/format.go
index ad9f91e4..afa7ee6a 100644
--- a/tools/goctl/api/format/format.go
+++ b/tools/goctl/api/format/format.go
@@ -171,7 +171,9 @@ func apiFormat(data string, skipCheckDeclare bool, filename ...string) (string,
 				tapCount++
 			}
 		}
-		util.WriteIndent(&builder, tapCount)
+		if line != "" {
+			util.WriteIndent(&builder, tapCount)
+		}
 		builder.WriteString(line + pathx.NL)
 		if strings.HasSuffix(noCommentLine, leftParenthesis) || strings.HasSuffix(noCommentLine, leftBrace) {
 			tapCount++


Test patch:
diff --git a/tools/goctl/api/format/format_test.go b/tools/goctl/api/format/format_test.go
index 076b25a8..786ac42b 100644
--- a/tools/goctl/api/format/format_test.go
+++ b/tools/goctl/api/format/format_test.go
@@ -26,6 +26,11 @@ service A-api {
 handler: GreetHandler
   )
   get /greet/from/:name(Request) returns (Response)
+
+@server(
+handler: GreetHandler2
+  )
+  get /greet/from2/:name(Request) returns (Response)
 }
 `
 
@@ -41,6 +46,11 @@ service A-api {
 		handler: GreetHandler
 	)
 	get /greet/from/:name(Request) returns (Response)
+
+	@server(
+		handler: GreetHandler2
+	)
+	get /greet/from2/:name(Request) returns (Response)
 }`
 )
Repository: zeromicro/go-zero
Pull request #3225 (2023-05-08 08:56:12)

fix: timeout handler not implementing http.Flusher
fix #3178 


Patch:
diff --git a/rest/handler/timeouthandler.go b/rest/handler/timeouthandler.go
index ec34180a..52d82a80 100644
--- a/rest/handler/timeouthandler.go
+++ b/rest/handler/timeouthandler.go
@@ -127,6 +127,12 @@ type timeoutWriter struct {
 
 var _ http.Pusher = (*timeoutWriter)(nil)
 
+func (tw *timeoutWriter) Flush() {
+	if flusher, ok := tw.w.(http.Flusher); ok {
+		flusher.Flush()
+	}
+}
+
 func (tw *timeoutWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
 	if hijacked, ok := tw.w.(http.Hijacker); ok {
 		return hijacked.Hijack()


Test patch:
diff --git a/rest/handler/timeouthandler_test.go b/rest/handler/timeouthandler_test.go
index 3fd8bdd2..acbda030 100644
--- a/rest/handler/timeouthandler_test.go
+++ b/rest/handler/timeouthandler_test.go
@@ -164,6 +164,24 @@ func TestTimeoutHijack(t *testing.T) {
 	})
 }
 
+func TestTimeoutFlush(t *testing.T) {
+	timeoutHandler := TimeoutHandler(time.Minute)
+	handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		flusher, ok := w.(http.Flusher)
+		if !ok {
+			http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
+			return
+		}
+
+		flusher.Flush()
+	}))
+
+	req := httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody)
+	resp := httptest.NewRecorder()
+	handler.ServeHTTP(resp, req)
+	assert.Equal(t, http.StatusOK, resp.Code)
+}
+
 func TestTimeoutPusher(t *testing.T) {
 	handler := &timeoutWriter{
 		w: mockedPusher{},
Repository: zeromicro/go-zero
Pull request #3229 (2023-05-08 15:17:05)

chore: add more tests

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/zeromicro/go-zero/pull/3229?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#3229](https://app.codecov.io/gh/zeromicro/go-zero/pull/3229?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (e695484) into [master](https://app.codecov.io/gh/zeromicro/go-zero/commit/851a72f1cc8a9163bef346695a3ce09c68e6a0b0?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (851a72f) will **increase** coverage by `0.05%`.
> The diff coverage is `100.00%`.


| [Impacted Files](https://app.codecov.io/gh/zeromicro/go-zero/pull/3229?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [core/rescue/recover.go](https://app.codecov.io/gh/zeromicro/go-zero/pull/3229?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-Y29yZS9yZXNjdWUvcmVjb3Zlci5nbw==) | `100.00% <100.00%> (+50.00%)` | :arrow_up: |

... and [1 file with indirect coverage changes](https://app.codecov.io/gh/zeromicro/go-zero/pull/3229/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)


Patch:
diff --git a/core/rescue/recover.go b/core/rescue/recover.go
index 5d2922bf..b9361b7c 100644
--- a/core/rescue/recover.go
+++ b/core/rescue/recover.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"runtime/debug"
 
-	"github.com/zeromicro/go-zero/core/logc"
 	"github.com/zeromicro/go-zero/core/logx"
 )
 
@@ -22,12 +21,13 @@ func Recover(cleanups ...func()) {
 	}
 }
 
+// RecoverCtx is used with defer to do cleanup on panics.
 func RecoverCtx(ctx context.Context, cleanups ...func()) {
 	for _, cleanup := range cleanups {
 		cleanup()
 	}
 
 	if p := recover(); p != nil {
-		logc.Errorf(ctx, "%+v\n\n%s", p, debug.Stack())
+		logx.WithContext(ctx).Errorf("%+v\n%s", p, debug.Stack())
 	}
 }


Test patch:
diff --git a/core/rescue/recover_test.go b/core/rescue/recover_test.go
index fa4cd33c..07816054 100644
--- a/core/rescue/recover_test.go
+++ b/core/rescue/recover_test.go
@@ -1,6 +1,7 @@
 package rescue
 
 import (
+	"context"
 	"sync/atomic"
 	"testing"
 
@@ -25,3 +26,17 @@ func TestRescue(t *testing.T) {
 	})
 	assert.Equal(t, int32(5), atomic.LoadInt32(&count))
 }
+
+func TestRescueCtx(t *testing.T) {
+	var count int32
+	assert.NotPanics(t, func() {
+		defer RecoverCtx(context.Background(), func() {
+			atomic.AddInt32(&count, 2)
+		}, func() {
+			atomic.AddInt32(&count, 3)
+		})
+
+		panic("hello")
+	})
+	assert.Equal(t, int32(5), atomic.LoadInt32(&count))
+}