Repository: zeromicro/go-zero
Pull request #3210 (2023-05-05 17:06:55)

[dart-gen] Fix lists containing atomic types
## Before
Before will, this code will throw an error: 
`Unhandled Exception: type 'List<dynamic>' is not a subtype of type 'List<String>'`.
```dart
  factory SampleResponse.fromJson(Map<String, dynamic> m) {
    return SampleResponse(
      type: m['type'] ?? "",
      imageUrls: m['imageUrls'] ?? [],               ### Code can compile, but it will throw an error during run-time.
    );
  }
```

## After
After fix, this code runs smoothly.
```dart
  factory SampleResponse.fromJson(Map<String, dynamic> m) {
    return SampleResponse(
      type: m['type'] ?? "",
      imageUrls: m['imageUrls']?.cast<String>() ?? [],
    );
  }
```

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


[see 2 files with indirect coverage changes](https://app.codecov.io/gh/zeromicro/go-zero/pull/3210/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/logx/richlogger.go b/core/logx/richlogger.go
index 6f2a826d..0357e397 100644
--- a/core/logx/richlogger.go
+++ b/core/logx/richlogger.go
@@ -65,7 +65,7 @@ func (l *richLogger) Errorf(format string, v ...any) {
 }
 
 func (l *richLogger) Errorv(v any) {
-	l.err(fmt.Sprint(v))
+	l.err(v)
 }
 
 func (l *richLogger) Errorw(msg string, fields ...LogField) {
diff --git a/core/rescue/recover.go b/core/rescue/recover.go
index 7cde054e..b9361b7c 100644
--- a/core/rescue/recover.go
+++ b/core/rescue/recover.go
@@ -1,6 +1,11 @@
 package rescue
 
-import "github.com/zeromicro/go-zero/core/logx"
+import (
+	"context"
+	"runtime/debug"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
 
 // Recover is used with defer to do cleanup on panics.
 // Use it like:
@@ -15,3 +20,14 @@ func Recover(cleanups ...func()) {
 		logx.ErrorStack(p)
 	}
 }
+
+// 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 {
+		logx.WithContext(ctx).Errorf("%+v\n%s", p, debug.Stack())
+	}
+}
diff --git a/core/stat/metrics.go b/core/stat/metrics.go
index f0efe1e9..5b0809b9 100644
--- a/core/stat/metrics.go
+++ b/core/stat/metrics.go
@@ -141,7 +141,7 @@ func (c *metricsContainer) Execute(v any) {
 			report.Median = float32(medianTask.Duration) / float32(time.Millisecond)
 			tenPercent := fiftyPercent / 5
 			if tenPercent > 0 {
-				top10pTasks := topK(tasks, tenPercent)
+				top10pTasks := topK(top50pTasks, tenPercent)
 				task90th := top10pTasks[0]
 				report.Top90th = float32(task90th.Duration) / float32(time.Millisecond)
 				onePercent := tenPercent / 10
@@ -163,7 +163,7 @@ func (c *metricsContainer) Execute(v any) {
 					report.Top99p9th = mostDuration
 				}
 			} else {
-				mostDuration := getTopDuration(tasks)
+				mostDuration := getTopDuration(top50pTasks)
 				report.Top90th = mostDuration
 				report.Top99th = mostDuration
 				report.Top99p9th = mostDuration
diff --git a/core/threading/routines.go b/core/threading/routines.go
index 900dcb3b..a165992f 100644
--- a/core/threading/routines.go
+++ b/core/threading/routines.go
@@ -2,6 +2,7 @@ package threading
 
 import (
 	"bytes"
+	"context"
 	"runtime"
 	"strconv"
 
@@ -13,6 +14,11 @@ func GoSafe(fn func()) {
 	go RunSafe(fn)
 }
 
+// GoSafeCtx runs the given fn using another goroutine, recovers if fn panics with ctx.
+func GoSafeCtx(ctx context.Context, fn func()) {
+	go RunSafeCtx(ctx, fn)
+}
+
 // RoutineId is only for debug, never use it in production.
 func RoutineId() uint64 {
 	b := make([]byte, 64)
@@ -31,3 +37,10 @@ func RunSafe(fn func()) {
 
 	fn()
 }
+
+// RunSafeCtx runs the given fn, recovers if fn panics with ctx.
+func RunSafeCtx(ctx context.Context, fn func()) {
+	defer rescue.RecoverCtx(ctx)
+
+	fn()
+}
diff --git a/core/trace/agent.go b/core/trace/agent.go
index 939c1d9e..9b293e45 100644
--- a/core/trace/agent.go
+++ b/core/trace/agent.go
@@ -71,17 +71,29 @@ func createExporter(c Config) (sdktrace.SpanExporter, error) {
 		// endpoint can not reach.
 		// If the connection not dial success, the global otel ErrorHandler will catch error
 		// when reporting data like other exporters.
-		return otlptracegrpc.New(
-			context.Background(),
+		opts := []otlptracegrpc.Option{
 			otlptracegrpc.WithInsecure(),
 			otlptracegrpc.WithEndpoint(c.Endpoint),
+		}
+		if len(c.OtlpHeaders) > 0 {
+			opts = append(opts, otlptracegrpc.WithHeaders(c.OtlpHeaders))
+		}
+		return otlptracegrpc.New(
+			context.Background(),
+			opts...,
 		)
 	case kindOtlpHttp:
 		// Not support flexible configuration now.
-		return otlptracehttp.New(
-			context.Background(),
+		opts := []otlptracehttp.Option{
 			otlptracehttp.WithInsecure(),
 			otlptracehttp.WithEndpoint(c.Endpoint),
+		}
+		if len(c.OtlpHeaders) > 0 {
+			opts = append(opts, otlptracehttp.WithHeaders(c.OtlpHeaders))
+		}
+		return otlptracehttp.New(
+			context.Background(),
+			opts...,
 		)
 	default:
 		return nil, fmt.Errorf("unknown exporter: %s", c.Batcher)
diff --git a/core/trace/config.go b/core/trace/config.go
index 83c62c3c..9e6f7fb0 100644
--- a/core/trace/config.go
+++ b/core/trace/config.go
@@ -5,8 +5,9 @@ const TraceName = "go-zero"
 
 // A Config is an opentelemetry config.
 type Config struct {
-	Name     string  `json:",optional"`
-	Endpoint string  `json:",optional"`
-	Sampler  float64 `json:",default=1.0"`
-	Batcher  string  `json:",default=jaeger,options=jaeger|zipkin|otlpgrpc|otlphttp"`
+	Name        string            `json:",optional"`
+	Endpoint    string            `json:",optional"`
+	Sampler     float64           `json:",default=1.0"`
+	Batcher     string            `json:",default=jaeger,options=jaeger|zipkin|otlpgrpc|otlphttp"`
+	OtlpHeaders map[string]string `json:",optional"` // uptrace-dsn: 'http://project2_secret_token@localhost:14317/2'
 }
diff --git a/go.mod b/go.mod
index 53dc839d..2b764a15 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,6 @@ require (
 	github.com/DATA-DOG/go-sqlmock v1.5.0
 	github.com/alicebob/miniredis/v2 v2.30.2
 	github.com/fatih/color v1.15.0
-	github.com/felixge/fgprof v0.9.3
 	github.com/fullstorydev/grpcurl v1.8.7
 	github.com/go-redis/redis/v8 v8.11.5
 	github.com/go-sql-driver/mysql v1.7.1
@@ -33,7 +32,7 @@ require (
 	go.opentelemetry.io/otel/trace v1.14.0
 	go.uber.org/automaxprocs v1.5.2
 	go.uber.org/goleak v1.2.1
-	golang.org/x/net v0.9.0
+	golang.org/x/net v0.10.0
 	golang.org/x/sys v0.8.0
 	golang.org/x/time v0.3.0
 	google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4
@@ -69,7 +68,6 @@ require (
 	github.com/google/gnostic v0.5.7-v3refs // indirect
 	github.com/google/go-cmp v0.5.9 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
-	github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 // indirect
 	github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
 	github.com/jackc/pgpassfile v1.0.0 // indirect
@@ -108,7 +106,7 @@ require (
 	golang.org/x/crypto v0.6.0 // indirect
 	golang.org/x/oauth2 v0.6.0 // indirect
 	golang.org/x/sync v0.1.0 // indirect
-	golang.org/x/term v0.7.0 // indirect
+	golang.org/x/term v0.8.0 // indirect
 	golang.org/x/text v0.9.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
diff --git a/go.sum b/go.sum
index 1cd8818a..404d97e6 100644
--- a/go.sum
+++ b/go.sum
@@ -89,8 +89,6 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
 github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
-github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
-github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fullstorydev/grpcurl v1.8.7 h1:xJWosq3BQovQ4QrdPO72OrPiWuGgEsxY8ldYsJbPrqI=
 github.com/fullstorydev/grpcurl v1.8.7/go.mod h1:pVtM4qe3CMoLaIzYS8uvTuDj2jVYmXqMUkZeijnXp/E=
@@ -183,8 +181,6 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
 github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
-github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
@@ -200,7 +196,6 @@ github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplb
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
 github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
 github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
 github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
@@ -433,8 +428,8 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
-golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -488,13 +483,12 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
-golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
+golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/internal/devserver/server.go b/internal/devserver/server.go
index 6f34413f..4b34f56c 100644
--- a/internal/devserver/server.go
+++ b/internal/devserver/server.go
@@ -7,7 +7,6 @@ import (
 	"net/http/pprof"
 	"sync"
 
-	"github.com/felixge/fgprof"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
 	"github.com/zeromicro/go-zero/core/logx"
 	"github.com/zeromicro/go-zero/core/prometheus"
@@ -49,7 +48,6 @@ func (s *Server) addRoutes() {
 	}
 	// pprof
 	if s.config.EnablePprof {
-		s.handleFunc("/debug/fgprof", fgprof.Handler().(http.HandlerFunc))
 		s.handleFunc("/debug/pprof/", pprof.Index)
 		s.handleFunc("/debug/pprof/cmdline", pprof.Cmdline)
 		s.handleFunc("/debug/pprof/profile", pprof.Profile)
diff --git a/rest/engine.go b/rest/engine.go
index 5f2ffd91..a206b3ce 100644
--- a/rest/engine.go
+++ b/rest/engine.go
@@ -136,7 +136,7 @@ func (ng *engine) buildChainWithNativeMiddlewares(fr featuredRoutes, route Route
 		chn = chn.Append(ng.getLogHandler())
 	}
 	if ng.conf.Middlewares.Prometheus {
-		chn = chn.Append(handler.PrometheusHandler(route.Path))
+		chn = chn.Append(handler.PrometheusHandler(route.Path, route.Method))
 	}
 	if ng.conf.Middlewares.MaxConns {
 		chn = chn.Append(handler.MaxConnsHandler(ng.conf.MaxConns))
diff --git a/rest/handler/prometheushandler.go b/rest/handler/prometheushandler.go
index fcafa0a1..c1aa7ae7 100644
--- a/rest/handler/prometheushandler.go
+++ b/rest/handler/prometheushandler.go
@@ -18,7 +18,7 @@ var (
 		Subsystem: "requests",
 		Name:      "duration_ms",
 		Help:      "http server requests duration(ms).",
-		Labels:    []string{"path"},
+		Labels:    []string{"path", "method"},
 		Buckets:   []float64{5, 10, 25, 50, 100, 250, 500, 1000},
 	})
 
@@ -27,19 +27,19 @@ var (
 		Subsystem: "requests",
 		Name:      "code_total",
 		Help:      "http server requests error count.",
-		Labels:    []string{"path", "code"},
+		Labels:    []string{"path", "code", "method"},
 	})
 )
 
 // PrometheusHandler returns a middleware that reports stats to prometheus.
-func PrometheusHandler(path string) func(http.Handler) http.Handler {
+func PrometheusHandler(path, method string) func(http.Handler) http.Handler {
 	return func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			startTime := timex.Now()
 			cw := &response.WithCodeResponseWriter{Writer: w}
 			defer func() {
-				metricServerReqDur.Observe(int64(timex.Since(startTime)/time.Millisecond), path)
-				metricServerReqCodeTotal.Inc(path, strconv.Itoa(cw.Code))
+				metricServerReqDur.Observe(int64(timex.Since(startTime)/time.Millisecond), path, method)
+				metricServerReqCodeTotal.Inc(path, strconv.Itoa(cw.Code), method)
 			}()
 
 			next.ServeHTTP(cw, r)
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()
diff --git a/tools/goctl/api/dartgen/gendata.go b/tools/goctl/api/dartgen/gendata.go
index 08ca1354..7894cfb3 100644
--- a/tools/goctl/api/dartgen/gendata.go
+++ b/tools/goctl/api/dartgen/gendata.go
@@ -45,8 +45,10 @@ class {{.Name}} {
 		return {{.Name}}(
 			{{range .Members}}
 				{{lowCamelCase .Name}}: {{appendNullCoalescing .}}
-					{{if isDirectType .Type.Name}}
+					{{if isAtomicType .Type.Name}}
 						m['{{getPropertyFromMember .}}'] {{appendDefaultEmptyValue .Type.Name}}
+					{{else if isAtomicListType .Type.Name}}
+						m['{{getPropertyFromMember .}}']?.cast<{{getCoreType .Type.Name}}>() {{appendDefaultEmptyValue .Type.Name}}
 					{{else if isClassListType .Type.Name}}
 						((m['{{getPropertyFromMember .}}'] {{appendDefaultEmptyValue .Type.Name}}) as List<dynamic>).map((i) => {{getCoreType .Type.Name}}.fromJson(i)).toList()
 					{{else}}
diff --git a/tools/goctl/api/dartgen/genvars.go b/tools/goctl/api/dartgen/genvars.go
index ac1b0fac..64cce8c5 100644
--- a/tools/goctl/api/dartgen/genvars.go
+++ b/tools/goctl/api/dartgen/genvars.go
@@ -2,7 +2,6 @@ package dartgen
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 )
 
@@ -83,7 +82,7 @@ func genVars(dir string, isLegacy bool, scheme string, hostname string) error {
 	}
 
 	if !fileExists(dir + "vars.dart") {
-		err = ioutil.WriteFile(dir+"vars.dart", []byte(fmt.Sprintf(`const serverHost='%s://%s';`, scheme, hostname)), 0o644)
+		err = os.WriteFile(dir+"vars.dart", []byte(fmt.Sprintf(`const serverHost='%s://%s';`, scheme, hostname)), 0o644)
 		if err != nil {
 			return err
 		}
@@ -94,7 +93,7 @@ func genVars(dir string, isLegacy bool, scheme string, hostname string) error {
 		if isLegacy {
 			tpl = varTemplate
 		}
-		err = ioutil.WriteFile(dir+"kv.dart", []byte(tpl), 0o644)
+		err = os.WriteFile(dir+"kv.dart", []byte(tpl), 0o644)
 		if err != nil {
 			return err
 		}
diff --git a/tools/goctl/api/dartgen/util.go b/tools/goctl/api/dartgen/util.go
index 0847057d..79d94e80 100644
--- a/tools/goctl/api/dartgen/util.go
+++ b/tools/goctl/api/dartgen/util.go
@@ -71,7 +71,11 @@ func isListType(s string) bool {
 }
 
 func isClassListType(s string) bool {
-	return strings.HasPrefix(s, "List<") && !isAtomicType(getCoreType(s))
+	return isListType(s) && !isAtomicType(getCoreType(s))
+}
+
+func isAtomicListType(s string) bool {
+	return isListType(s) && isAtomicType(getCoreType(s))
 }
 
 func isListItemsNullable(s string) bool {
diff --git a/tools/goctl/api/dartgen/vars.go b/tools/goctl/api/dartgen/vars.go
index 233a889c..3dcff8e7 100644
--- a/tools/goctl/api/dartgen/vars.go
+++ b/tools/goctl/api/dartgen/vars.go
@@ -6,8 +6,10 @@ var funcMap = template.FuncMap{
 	"getBaseName":                     getBaseName,
 	"getPropertyFromMember":           getPropertyFromMember,
 	"isDirectType":                    isDirectType,
+	"isAtomicType":                    isAtomicType,
 	"isNumberType":                    isNumberType,
 	"isClassListType":                 isClassListType,
+	"isAtomicListType":                isAtomicListType,
 	"isListItemsNullable":             isListItemsNullable,
 	"isNullableType":                  isNullableType,
 	"appendNullCoalescing":            appendNullCoalescing,
diff --git a/tools/goctl/migrate/migrate.go b/tools/goctl/migrate/migrate.go
index 4f4b80a0..4eee9459 100644
--- a/tools/goctl/migrate/migrate.go
+++ b/tools/goctl/migrate/migrate.go
@@ -8,7 +8,6 @@ import (
 	"go/parser"
 	"go/token"
 	"io/fs"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -163,7 +162,7 @@ func writeFile(pkgs []*ast.Package, verbose bool) error {
 				return fmt.Errorf("[rewriteImport] format file %s error: %w", filename, err)
 			}
 
-			err = ioutil.WriteFile(filename, w.Bytes(), os.ModePerm)
+			err = os.WriteFile(filename, w.Bytes(), os.ModePerm)
 			if err != nil {
 				return fmt.Errorf("[rewriteImport] write file %s error: %w", filename, err)
 			}
diff --git a/tools/goctl/migrate/version.go b/tools/goctl/migrate/version.go
index de6b8fb0..37a4eec0 100644
--- a/tools/goctl/migrate/version.go
+++ b/tools/goctl/migrate/version.go
@@ -2,7 +2,7 @@ package migrate
 
 import (
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strings"
 	"time"
@@ -34,7 +34,7 @@ func getLatest(repo string, verbose bool) ([]string, error) {
 			continue
 		}
 		defer resp.Body.Close()
-		data, err := ioutil.ReadAll(resp.Body)
+		data, err := io.ReadAll(resp.Body)
 		if err != nil {
 			log(err)
 			continue
diff --git a/tools/goctl/model/sql/command/migrationnotes/v1.3.4.go b/tools/goctl/model/sql/command/migrationnotes/v1.3.4.go
index 0f5cb759..dc9df2c5 100644
--- a/tools/goctl/model/sql/command/migrationnotes/v1.3.4.go
+++ b/tools/goctl/model/sql/command/migrationnotes/v1.3.4.go
@@ -2,7 +2,7 @@ package migrationnotes
 
 import (
 	"fmt"
-	"io/ioutil"
+	"os"
 	"strings"
 )
 
@@ -24,7 +24,7 @@ func migrateBefore1_3_4(dir, style string) error {
 }
 
 func needShow1_3_4(dir, style string) (bool, error) {
-	files, err := ioutil.ReadDir(dir)
+	files, err := os.ReadDir(dir)
 	if err != nil {
 		return false, nil
 	}
diff --git a/tools/goctl/model/sql/gen/gen.go b/tools/goctl/model/sql/gen/gen.go
index 5b7bfea3..34a07517 100644
--- a/tools/goctl/model/sql/gen/gen.go
+++ b/tools/goctl/model/sql/gen/gen.go
@@ -3,7 +3,6 @@ package gen
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -168,7 +167,7 @@ func (g *defaultGenerator) createFile(modelList map[string]*codeTuple) error {
 
 		name := util.SafeString(modelFilename) + "_gen.go"
 		filename := filepath.Join(dirAbs, name)
-		err = ioutil.WriteFile(filename, []byte(codes.modelCode), os.ModePerm)
+		err = os.WriteFile(filename, []byte(codes.modelCode), os.ModePerm)
 		if err != nil {
 			return err
 		}
@@ -179,7 +178,7 @@ func (g *defaultGenerator) createFile(modelList map[string]*codeTuple) error {
 			g.Warning("%s already exists, ignored.", name)
 			continue
 		}
-		err = ioutil.WriteFile(filename, []byte(codes.modelCustomCode), os.ModePerm)
+		err = os.WriteFile(filename, []byte(codes.modelCustomCode), os.ModePerm)
 		if err != nil {
 			return err
 		}
diff --git a/tools/goctl/model/sql/util/matcher.go b/tools/goctl/model/sql/util/matcher.go
index b7e84e05..5824426f 100644
--- a/tools/goctl/model/sql/util/matcher.go
+++ b/tools/goctl/model/sql/util/matcher.go
@@ -1,7 +1,7 @@
 package util
 
 import (
-	"io/ioutil"
+	"os"
 	"path/filepath"
 )
 
@@ -13,7 +13,7 @@ func MatchFiles(in string) ([]string, error) {
 		return nil, err
 	}
 
-	files, err := ioutil.ReadDir(abs)
+	files, err := os.ReadDir(abs)
 	if err != nil {
 		return nil, err
 	}
diff --git a/tools/goctl/pkg/parser/api/format/format.go b/tools/goctl/pkg/parser/api/format/format.go
index e7b3aa72..128580c7 100644
--- a/tools/goctl/pkg/parser/api/format/format.go
+++ b/tools/goctl/pkg/parser/api/format/format.go
@@ -3,14 +3,14 @@ package format
 import (
 	"bytes"
 	"io"
-	"io/ioutil"
+	"os"
 
 	"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/parser"
 )
 
 // File formats the api file.
 func File(filename string) error {
-	data, err := ioutil.ReadFile(filename)
+	data, err := os.ReadFile(filename)
 	if err != nil {
 		return err
 	}
@@ -18,7 +18,7 @@ func File(filename string) error {
 	if err := Source(data, buffer); err != nil {
 		return err
 	}
-	return ioutil.WriteFile(filename, buffer.Bytes(), 0666)
+	return os.WriteFile(filename, buffer.Bytes(), 0666)
 }
 
 // Source formats the api source.
diff --git a/tools/goctl/pkg/parser/api/scanner/scanner.go b/tools/goctl/pkg/parser/api/scanner/scanner.go
index 802684c6..35a9332d 100644
--- a/tools/goctl/pkg/parser/api/scanner/scanner.go
+++ b/tools/goctl/pkg/parser/api/scanner/scanner.go
@@ -4,8 +4,8 @@ import (
 	"bytes"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"log"
+	"os"
 	"path/filepath"
 	"strings"
 
@@ -647,7 +647,7 @@ func NewScanner(filename string, src interface{}) (*Scanner, error) {
 }
 
 func readData(filename string, src interface{}) ([]byte, error) {
-	data, err := ioutil.ReadFile(filename)
+	data, err := os.ReadFile(filename)
 	if err == nil {
 		return data, nil
 	}
diff --git a/tools/goctl/plugin/plugin.go b/tools/goctl/plugin/plugin.go
index 321d6132..cf10685b 100644
--- a/tools/goctl/plugin/plugin.go
+++ b/tools/goctl/plugin/plugin.go
@@ -6,7 +6,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"os"
 	"os/exec"
@@ -171,7 +170,7 @@ func downloadFile(filepath, url string) error {
 // NewPlugin returns contextual resources when written in other languages
 func NewPlugin() (*Plugin, error) {
 	var plugin Plugin
-	content, err := ioutil.ReadAll(os.Stdin)
+	content, err := io.ReadAll(os.Stdin)
 	if err != nil {
 		return nil, err
 	}
diff --git a/tools/goctl/quickstart/micro.go b/tools/goctl/quickstart/micro.go
index 4d73af8e..ad776440 100644
--- a/tools/goctl/quickstart/micro.go
+++ b/tools/goctl/quickstart/micro.go
@@ -2,7 +2,7 @@ package quickstart
 
 import (
 	_ "embed"
-	"io/ioutil"
+	"os"
 	"path/filepath"
 
 	"github.com/zeromicro/go-zero/core/logx"
@@ -38,7 +38,7 @@ func initRPCProto() error {
 
 	protoFilename := filepath.Join(zrpcWorkDir, protoName)
 	rpcBytes := []byte(protocContent)
-	return ioutil.WriteFile(protoFilename, rpcBytes, 0o666)
+	return os.WriteFile(protoFilename, rpcBytes, 0o666)
 }
 
 type micro struct{}
@@ -55,7 +55,7 @@ func (m micro) mustStartRPCProject() {
 	arg := "goctl rpc protoc " + protoName + " --go_out=. --go-grpc_out=. --zrpc_out=. --verbose"
 	execCommand(zrpcWorkDir, arg)
 	etcFile := filepath.Join(zrpcWorkDir, "etc", "greet.yaml")
-	logx.Must(ioutil.WriteFile(etcFile, []byte(rpcEtcContent), 0o666))
+	logx.Must(os.WriteFile(etcFile, []byte(rpcEtcContent), 0o666))
 }
 
 func (m micro) start() {
diff --git a/tools/goctl/quickstart/mono.go b/tools/goctl/quickstart/mono.go
index 4710b1b3..5d04bf5d 100644
--- a/tools/goctl/quickstart/mono.go
+++ b/tools/goctl/quickstart/mono.go
@@ -2,7 +2,7 @@ package quickstart
 
 import (
 	_ "embed"
-	"io/ioutil"
+	"os"
 	"path/filepath"
 
 	"github.com/zeromicro/go-zero/core/logx"
@@ -35,7 +35,7 @@ func initAPIFlags() error {
 
 	apiFilename := filepath.Join(apiWorkDir, "greet.api")
 	apiBytes := []byte(apiContent)
-	if err := ioutil.WriteFile(apiFilename, apiBytes, 0o666); err != nil {
+	if err := os.WriteFile(apiFilename, apiBytes, 0o666); err != nil {
 		return err
 	}
 
@@ -59,7 +59,7 @@ func (m mono) createAPIProject() {
 	log.Debug(">> Generating quickstart api project...")
 	logx.Must(gogen.GoCommand(nil, nil))
 	etcFile := filepath.Join(apiWorkDir, "etc", "greet.yaml")
-	logx.Must(ioutil.WriteFile(etcFile, []byte(apiEtcContent), 0o666))
+	logx.Must(os.WriteFile(etcFile, []byte(apiEtcContent), 0o666))
 	logicFile := filepath.Join(apiWorkDir, "internal", "logic", "pinglogic.go")
 	svcFile := filepath.Join(apiWorkDir, "internal", "svc", "servicecontext.go")
 	configPath := filepath.Join(apiWorkDir, "internal", "config")
diff --git a/tools/goctl/rpc/generator/genconfig.go b/tools/goctl/rpc/generator/genconfig.go
index a81a07a8..2af78496 100644
--- a/tools/goctl/rpc/generator/genconfig.go
+++ b/tools/goctl/rpc/generator/genconfig.go
@@ -2,7 +2,6 @@ package generator
 
 import (
 	_ "embed"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 
@@ -36,5 +35,5 @@ func (g *Generator) GenConfig(ctx DirContext, _ parser.Proto, cfg *conf.Config)
 		return err
 	}
 
-	return ioutil.WriteFile(fileName, []byte(text), os.ModePerm)
+	return os.WriteFile(fileName, []byte(text), os.ModePerm)
 }
diff --git a/tools/goctl/util/pathx/file.go b/tools/goctl/util/pathx/file.go
index 6cbd037d..3121a19f 100644
--- a/tools/goctl/util/pathx/file.go
+++ b/tools/goctl/util/pathx/file.go
@@ -6,7 +6,7 @@ import (
 	"encoding/hex"
 	"fmt"
 	"io"
-	"io/ioutil"
+	"io/fs"
 	"log"
 	"os"
 	"path/filepath"
@@ -145,9 +145,20 @@ func GetTemplateDir(category string) (string, error) {
 		// backward compatible, it will be removed in the feature
 		// backward compatible start.
 		beforeTemplateDir := filepath.Join(home, version.GetGoctlVersion(), category)
-		fs, _ := ioutil.ReadDir(beforeTemplateDir)
+		entries, err := os.ReadDir(beforeTemplateDir)
+		if err != nil {
+			return "", err
+		}
+		infos := make([]fs.FileInfo, 0, len(entries))
+		for _, entry := range entries {
+			info, err := entry.Info()
+			if err != nil {
+				return "", err
+			}
+			infos = append(infos, info)
+		}
 		var hasContent bool
-		for _, e := range fs {
+		for _, e := range infos {
 			if e.Size() > 0 {
 				hasContent = true
 			}
@@ -256,7 +267,7 @@ func createTemplate(file, content string, force bool) error {
 
 // MustTempDir creates a temporary directory.
 func MustTempDir() string {
-	dir, err := ioutil.TempDir("", "")
+	dir, err := os.MkdirTemp("", "")
 	if err != nil {
 		log.Fatalln(err)
 	}
diff --git a/tools/goctl/util/templatex.go b/tools/goctl/util/templatex.go
index 981a6af6..726544c5 100644
--- a/tools/goctl/util/templatex.go
+++ b/tools/goctl/util/templatex.go
@@ -3,7 +3,7 @@ package util
 import (
 	"bytes"
 	goformat "go/format"
-	"io/ioutil"
+	"os"
 	"regexp"
 	"text/template"
 
@@ -50,7 +50,7 @@ func (t *DefaultTemplate) SaveTo(data any, path string, forceUpdate bool) error
 		return err
 	}
 
-	return ioutil.WriteFile(path, output.Bytes(), regularPerm)
+	return os.WriteFile(path, output.Bytes(), regularPerm)
 }
 
 // Execute returns the codes after the template executed


Test patch:
diff --git a/core/logx/richlogger_test.go b/core/logx/richlogger_test.go
index b629c537..11f7dfcb 100644
--- a/core/logx/richlogger_test.go
+++ b/core/logx/richlogger_test.go
@@ -66,6 +66,9 @@ func TestTraceDebug(t *testing.T) {
 	l.WithDuration(time.Second).Debugv(testlog)
 	validate(t, w.String(), true, true)
 	w.Reset()
+	l.WithDuration(time.Second).Debugv(testobj)
+	validateContentType(t, w.String(), map[string]any{}, true, true)
+	w.Reset()
 	l.WithDuration(time.Second).Debugw(testlog, Field("foo", "bar"))
 	validate(t, w.String(), true, true)
 	assert.True(t, strings.Contains(w.String(), "foo"), w.String())
@@ -103,6 +106,9 @@ func TestTraceError(t *testing.T) {
 	l.WithDuration(time.Second).Errorv(testlog)
 	validate(t, w.String(), true, true)
 	w.Reset()
+	l.WithDuration(time.Second).Errorv(testobj)
+	validateContentType(t, w.String(), map[string]any{}, true, true)
+	w.Reset()
 	l.WithDuration(time.Second).Errorw(testlog, Field("basket", "ball"))
 	validate(t, w.String(), true, true)
 	assert.True(t, strings.Contains(w.String(), "basket"), w.String())
@@ -137,6 +143,9 @@ func TestTraceInfo(t *testing.T) {
 	l.WithDuration(time.Second).Infov(testlog)
 	validate(t, w.String(), true, true)
 	w.Reset()
+	l.WithDuration(time.Second).Infov(testobj)
+	validateContentType(t, w.String(), map[string]any{}, true, true)
+	w.Reset()
 	l.WithDuration(time.Second).Infow(testlog, Field("basket", "ball"))
 	validate(t, w.String(), true, true)
 	assert.True(t, strings.Contains(w.String(), "basket"), w.String())
@@ -173,6 +182,9 @@ func TestTraceInfoConsole(t *testing.T) {
 	w.Reset()
 	l.WithDuration(time.Second).Infov(testlog)
 	validate(t, w.String(), true, true)
+	w.Reset()
+	l.WithDuration(time.Second).Infov(testobj)
+	validateContentType(t, w.String(), map[string]any{}, true, true)
 }
 
 func TestTraceSlow(t *testing.T) {
@@ -204,6 +216,9 @@ func TestTraceSlow(t *testing.T) {
 	l.WithDuration(time.Second).Slowv(testlog)
 	validate(t, w.String(), true, true)
 	w.Reset()
+	l.WithDuration(time.Second).Slowv(testobj)
+	validateContentType(t, w.String(), map[string]any{}, true, true)
+	w.Reset()
 	l.WithDuration(time.Second).Sloww(testlog, Field("basket", "ball"))
 	validate(t, w.String(), true, true)
 	assert.True(t, strings.Contains(w.String(), "basket"), w.String())
@@ -311,8 +326,32 @@ func validate(t *testing.T, body string, expectedTrace, expectedSpan bool) {
 	assert.Equal(t, expectedSpan, len(val.Span) > 0, body)
 }
 
+func validateContentType(t *testing.T, body string, expectedType any, expectedTrace, expectedSpan bool) {
+	var val mockValue
+	dec := json.NewDecoder(strings.NewReader(body))
+
+	for {
+		var doc mockValue
+		err := dec.Decode(&doc)
+		if err == io.EOF {
+			// all done
+			break
+		}
+		if err != nil {
+			continue
+		}
+
+		val = doc
+	}
+
+	assert.IsType(t, expectedType, val.Content, body)
+	assert.Equal(t, expectedTrace, len(val.Trace) > 0, body)
+	assert.Equal(t, expectedSpan, len(val.Span) > 0, body)
+}
+
 type mockValue struct {
-	Trace string `json:"trace"`
-	Span  string `json:"span"`
-	Foo   string `json:"foo"`
+	Trace   string `json:"trace"`
+	Span    string `json:"span"`
+	Foo     string `json:"foo"`
+	Content any    `json:"content"`
 }
diff --git a/core/logx/syslog_test.go b/core/logx/syslog_test.go
index 8591f16c..eb6d757a 100644
--- a/core/logx/syslog_test.go
+++ b/core/logx/syslog_test.go
@@ -12,6 +12,8 @@ import (
 
 const testlog = "Stay hungry, stay foolish."
 
+var testobj = map[string]any{"foo": "bar"}
+
 func TestCollectSysLog(t *testing.T) {
 	CollectSysLog()
 	content := getContent(captureOutput(func() {
diff --git a/core/mr/mapreduce_test.go b/core/mr/mapreduce_test.go
index 00418cc8..60b1427c 100644
--- a/core/mr/mapreduce_test.go
+++ b/core/mr/mapreduce_test.go
@@ -3,7 +3,7 @@ package mr
 import (
 	"context"
 	"errors"
-	"io/ioutil"
+	"io"
 	"log"
 	"runtime"
 	"sync/atomic"
@@ -17,7 +17,7 @@ import (
 var errDummy = errors.New("dummy")
 
 func init() {
-	log.SetOutput(ioutil.Discard)
+	log.SetOutput(io.Discard)
 }
 
 func TestFinish(t *testing.T) {
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))
+}
diff --git a/core/threading/routines_test.go b/core/threading/routines_test.go
index 6a12dca0..30a524ee 100644
--- a/core/threading/routines_test.go
+++ b/core/threading/routines_test.go
@@ -1,12 +1,15 @@
 package threading
 
 import (
+	"bytes"
+	"context"
 	"io"
 	"log"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/zeromicro/go-zero/core/lang"
+	"github.com/zeromicro/go-zero/core/logx"
 )
 
 func TestRoutineId(t *testing.T) {
@@ -34,3 +37,51 @@ func TestRunSafe(t *testing.T) {
 	<-ch
 	i++
 }
+
+func TestRunSafeCtx(t *testing.T) {
+	var buf bytes.Buffer
+	logx.SetWriter(logx.NewWriter(&buf))
+	ctx := context.Background()
+	ch := make(chan lang.PlaceholderType)
+
+	i := 0
+
+	defer func() {
+		assert.Equal(t, 1, i)
+	}()
+
+	go RunSafeCtx(ctx, func() {
+		defer func() {
+			ch <- lang.Placeholder
+		}()
+
+		panic("panic")
+	})
+
+	<-ch
+	i++
+}
+
+func TestGoSafeCtx(t *testing.T) {
+	var buf bytes.Buffer
+	logx.SetWriter(logx.NewWriter(&buf))
+	ctx := context.Background()
+	ch := make(chan lang.PlaceholderType)
+
+	i := 0
+
+	defer func() {
+		assert.Equal(t, 1, i)
+	}()
+
+	GoSafeCtx(ctx, func() {
+		defer func() {
+			ch <- lang.Placeholder
+		}()
+
+		panic("panic")
+	})
+
+	<-ch
+	i++
+}
diff --git a/core/trace/agent_test.go b/core/trace/agent_test.go
index 853fa6ba..a3871a42 100644
--- a/core/trace/agent_test.go
+++ b/core/trace/agent_test.go
@@ -36,14 +36,20 @@ func TestStartAgent(t *testing.T) {
 		Batcher:  "otlp",
 	}
 	c5 := Config{
-		Name:     "grpc",
+		Name:     "otlpgrpc",
 		Endpoint: endpoint3,
 		Batcher:  kindOtlpGrpc,
+		OtlpHeaders: map[string]string{
+			"uptrace-dsn": "http://project2_secret_token@localhost:14317/2",
+		},
 	}
 	c6 := Config{
 		Name:     "otlphttp",
 		Endpoint: endpoint4,
 		Batcher:  kindOtlpHttp,
+		OtlpHeaders: map[string]string{
+			"uptrace-dsn": "http://project2_secret_token@localhost:14318/2",
+		},
 	}
 	c7 := Config{
 		Name:     "UDP",
diff --git a/rest/handler/prometheushandler_test.go b/rest/handler/prometheushandler_test.go
index ce7551e8..c0c1dec2 100644
--- a/rest/handler/prometheushandler_test.go
+++ b/rest/handler/prometheushandler_test.go
@@ -10,7 +10,7 @@ import (
 )
 
 func TestPromMetricHandler_Disabled(t *testing.T) {
-	promMetricHandler := PrometheusHandler("/user/login")
+	promMetricHandler := PrometheusHandler("/user/login", http.MethodGet)
 	handler := promMetricHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		w.WriteHeader(http.StatusOK)
 	}))
@@ -26,7 +26,7 @@ func TestPromMetricHandler_Enabled(t *testing.T) {
 		Host: "localhost",
 		Path: "/",
 	})
-	promMetricHandler := PrometheusHandler("/user/login")
+	promMetricHandler := PrometheusHandler("/user/login", http.MethodGet)
 	handler := promMetricHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		w.WriteHeader(http.StatusOK)
 	}))
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{},
diff --git a/tools/goctl/api/format/format_test.go b/tools/goctl/api/format/format_test.go
index 786ac42b..da49bf1c 100644
--- a/tools/goctl/api/format/format_test.go
+++ b/tools/goctl/api/format/format_test.go
@@ -3,7 +3,6 @@ package format
 import (
 	"fmt"
 	"io/fs"
-	"io/ioutil"
 	"os"
 	"path"
 	"testing"
@@ -71,11 +70,11 @@ func Test_apiFormatReader_issue1721(t *testing.T) {
 	require.NoError(t, err)
 
 	importedFilename := path.Join(dir, "foo.api")
-	err = ioutil.WriteFile(importedFilename, []byte{}, fs.ModePerm)
+	err = os.WriteFile(importedFilename, []byte{}, fs.ModePerm)
 	require.NoError(t, err)
 
 	filename := path.Join(subDir, "bar.api")
-	err = ioutil.WriteFile(filename, []byte(fmt.Sprintf(`import "%s"`, importedFilename)), 0o644)
+	err = os.WriteFile(filename, []byte(fmt.Sprintf(`import "%s"`, importedFilename)), 0o644)
 	require.NoError(t, err)
 
 	f, err := os.Open(filename)
diff --git a/tools/goctl/api/parser/g4/ast/apiparser_test.go b/tools/goctl/api/parser/g4/ast/apiparser_test.go
index f873f397..71d73650 100644
--- a/tools/goctl/api/parser/g4/ast/apiparser_test.go
+++ b/tools/goctl/api/parser/g4/ast/apiparser_test.go
@@ -1,7 +1,6 @@
 package ast
 
 import (
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"testing"
@@ -25,13 +24,13 @@ func Test_ImportCycle(t *testing.T) {
 	defer os.RemoveAll(dir)
 
 	mainPath := filepath.Join(dir, mainFilename)
-	err = ioutil.WriteFile(mainPath, []byte(mainSrc), 0o777)
+	err = os.WriteFile(mainPath, []byte(mainSrc), 0o777)
 	require.NoError(t, err)
 	subAPath := filepath.Join(dir, subAFilename)
-	err = ioutil.WriteFile(subAPath, []byte(subASrc), 0o777)
+	err = os.WriteFile(subAPath, []byte(subASrc), 0o777)
 	require.NoError(t, err)
 	subBPath := filepath.Join(dir, subBFilename)
-	err = ioutil.WriteFile(subBPath, []byte(subBSrc), 0o777)
+	err = os.WriteFile(subBPath, []byte(subBSrc), 0o777)
 	require.NoError(t, err)
 
 	_, err = NewParser().Parse(mainPath)
@@ -55,13 +54,13 @@ func Test_MultiImportedShouldAllowed(t *testing.T) {
 	defer os.RemoveAll(dir)
 
 	mainPath := filepath.Join(dir, mainFilename)
-	err = ioutil.WriteFile(mainPath, []byte(mainSrc), 0o777)
+	err = os.WriteFile(mainPath, []byte(mainSrc), 0o777)
 	require.NoError(t, err)
 	subAPath := filepath.Join(dir, subAFilename)
-	err = ioutil.WriteFile(subAPath, []byte(subASrc), 0o777)
+	err = os.WriteFile(subAPath, []byte(subASrc), 0o777)
 	require.NoError(t, err)
 	subBPath := filepath.Join(dir, subBFilename)
-	err = ioutil.WriteFile(subBPath, []byte(subBSrc), 0o777)
+	err = os.WriteFile(subBPath, []byte(subBSrc), 0o777)
 	require.NoError(t, err)
 
 	_, err = NewParser().Parse(mainPath)
@@ -84,13 +83,13 @@ func Test_RedundantDeclarationShouldNotBeAllowed(t *testing.T) {
 	defer os.RemoveAll(dir)
 
 	mainPath := filepath.Join(dir, mainFilename)
-	err = ioutil.WriteFile(mainPath, []byte(mainSrc), 0o777)
+	err = os.WriteFile(mainPath, []byte(mainSrc), 0o777)
 	require.NoError(t, err)
 	subAPath := filepath.Join(dir, subAFilename)
-	err = ioutil.WriteFile(subAPath, []byte(subASrc), 0o777)
+	err = os.WriteFile(subAPath, []byte(subASrc), 0o777)
 	require.NoError(t, err)
 	subBPath := filepath.Join(dir, subBFilename)
-	err = ioutil.WriteFile(subBPath, []byte(subBSrc), 0o777)
+	err = os.WriteFile(subBPath, []byte(subBSrc), 0o777)
 	require.NoError(t, err)
 
 	_, err = NewParser().Parse(mainPath)
diff --git a/tools/goctl/api/parser/g4/test/apiparser_test.go b/tools/goctl/api/parser/g4/test/apiparser_test.go
index 764ef1fd..e00ba69a 100644
--- a/tools/goctl/api/parser/g4/test/apiparser_test.go
+++ b/tools/goctl/api/parser/g4/test/apiparser_test.go
@@ -2,7 +2,6 @@ package test
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"testing"
@@ -120,7 +119,7 @@ func TestApiParser(t *testing.T) {
 
 	t.Run("nestedImport", func(t *testing.T) {
 		file := filepath.Join(pathx.MustTempDir(), "foo.api")
-		err := ioutil.WriteFile(file, []byte(nestedAPIImport), os.ModePerm)
+		err := os.WriteFile(file, []byte(nestedAPIImport), os.ModePerm)
 		if err != nil {
 			return
 		}
@@ -150,7 +149,7 @@ func TestApiParser(t *testing.T) {
 
 	t.Run("ambiguousSyntax", func(t *testing.T) {
 		file := filepath.Join(pathx.MustTempDir(), "foo.api")
-		err := ioutil.WriteFile(file, []byte(ambiguousSyntax), os.ModePerm)
+		err := os.WriteFile(file, []byte(ambiguousSyntax), os.ModePerm)
 		if err != nil {
 			return
 		}
@@ -164,7 +163,7 @@ func TestApiParser(t *testing.T) {
 
 	t.Run("ambiguousSyntax", func(t *testing.T) {
 		file := filepath.Join(pathx.MustTempDir(), "foo.api")
-		err := ioutil.WriteFile(file, []byte(ambiguousSyntax), os.ModePerm)
+		err := os.WriteFile(file, []byte(ambiguousSyntax), os.ModePerm)
 		if err != nil {
 			return
 		}
@@ -178,7 +177,7 @@ func TestApiParser(t *testing.T) {
 
 	t.Run("ambiguousService", func(t *testing.T) {
 		file := filepath.Join(pathx.MustTempDir(), "foo.api")
-		err := ioutil.WriteFile(file, []byte(ambiguousService), os.ModePerm)
+		err := os.WriteFile(file, []byte(ambiguousService), os.ModePerm)
 		if err != nil {
 			return
 		}
@@ -208,7 +207,7 @@ func TestApiParser(t *testing.T) {
 		assert.Error(t, err)
 
 		file := filepath.Join(pathx.MustTempDir(), "foo.api")
-		err = ioutil.WriteFile(file, []byte(duplicateHandler), os.ModePerm)
+		err = os.WriteFile(file, []byte(duplicateHandler), os.ModePerm)
 		if err != nil {
 			return
 		}
@@ -237,7 +236,7 @@ func TestApiParser(t *testing.T) {
 		assert.Error(t, err)
 
 		file := filepath.Join(pathx.MustTempDir(), "foo.api")
-		err = ioutil.WriteFile(file, []byte(duplicateRoute), os.ModePerm)
+		err = os.WriteFile(file, []byte(duplicateRoute), os.ModePerm)
 		if err != nil {
 			return
 		}
@@ -261,7 +260,7 @@ func TestApiParser(t *testing.T) {
 		assert.Error(t, err)
 
 		file := filepath.Join(pathx.MustTempDir(), "foo.api")
-		err = ioutil.WriteFile(file, []byte(duplicateType), os.ModePerm)
+		err = os.WriteFile(file, []byte(duplicateType), os.ModePerm)
 		if err != nil {
 			return
 		}
diff --git a/tools/goctl/compare/testdata/testdata.go b/tools/goctl/compare/testdata/testdata.go
index efed937c..c11b3d0a 100644
--- a/tools/goctl/compare/testdata/testdata.go
+++ b/tools/goctl/compare/testdata/testdata.go
@@ -2,7 +2,6 @@ package testdata
 
 import (
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"os/exec"
@@ -78,7 +77,7 @@ func mustGetTestData(baseDir string) (Files, Files) {
 			return data, nil
 		}
 
-		return data, ioutil.WriteFile(fp, []byte(data.Content), os.ModePerm)
+		return data, os.WriteFile(fp, []byte(data.Content), os.ModePerm)
 	}
 	oldDir := filepath.Join(baseDir, "old_fs")
 	newDir := filepath.Join(baseDir, "new_fs")
diff --git a/tools/goctl/model/mongo/generate/generate_test.go b/tools/goctl/model/mongo/generate/generate_test.go
index 3586b60a..2ea6c758 100644
--- a/tools/goctl/model/mongo/generate/generate_test.go
+++ b/tools/goctl/model/mongo/generate/generate_test.go
@@ -1,7 +1,7 @@
 package generate
 
 import (
-	"io/ioutil"
+	"os"
 	"path/filepath"
 	"testing"
 
@@ -22,7 +22,7 @@ func TestDo(t *testing.T) {
 
 		tempDir := pathx.MustTempDir()
 		typesfile := filepath.Join(tempDir, "types.go")
-		err = ioutil.WriteFile(typesfile, []byte(testTypes), 0o666)
+		err = os.WriteFile(typesfile, []byte(testTypes), 0o666)
 		assert.Nil(t, err)
 
 		err = Do(&Context{
@@ -38,7 +38,7 @@ func TestDo(t *testing.T) {
 	t.Run("missing config", func(t *testing.T) {
 		tempDir := t.TempDir()
 		typesfile := filepath.Join(tempDir, "types.go")
-		err := ioutil.WriteFile(typesfile, []byte(testTypes), 0o666)
+		err := os.WriteFile(typesfile, []byte(testTypes), 0o666)
 		assert.Nil(t, err)
 
 		err = Do(&Context{
@@ -54,7 +54,7 @@ func TestDo(t *testing.T) {
 		cfg := &config.Config{NamingFormat: "foo"}
 		tempDir := t.TempDir()
 		typesfile := filepath.Join(tempDir, "types.go")
-		err := ioutil.WriteFile(typesfile, []byte(testTypes), 0o666)
+		err := os.WriteFile(typesfile, []byte(testTypes), 0o666)
 		assert.Nil(t, err)
 
 		err = Do(&Context{
diff --git a/tools/goctl/model/sql/command/command_test.go b/tools/goctl/model/sql/command/command_test.go
index 330a6630..fa85e6e8 100644
--- a/tools/goctl/model/sql/command/command_test.go
+++ b/tools/goctl/model/sql/command/command_test.go
@@ -2,7 +2,6 @@ package command
 
 import (
 	_ "embed"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"sort"
@@ -76,12 +75,12 @@ func TestFromDDl(t *testing.T) {
 	user1Sql := filepath.Join(tempDir, "user1.sql")
 	user2Sql := filepath.Join(tempDir, "user2.sql")
 
-	err = ioutil.WriteFile(user1Sql, []byte(sql), os.ModePerm)
+	err = os.WriteFile(user1Sql, []byte(sql), os.ModePerm)
 	if err != nil {
 		return
 	}
 
-	err = ioutil.WriteFile(user2Sql, []byte(sql), os.ModePerm)
+	err = os.WriteFile(user2Sql, []byte(sql), os.ModePerm)
 	if err != nil {
 		return
 	}
diff --git a/tools/goctl/model/sql/command/migrationnotes/v1.3.4_test.go b/tools/goctl/model/sql/command/migrationnotes/v1.3.4_test.go
index 4d77bbfc..80642833 100644
--- a/tools/goctl/model/sql/command/migrationnotes/v1.3.4_test.go
+++ b/tools/goctl/model/sql/command/migrationnotes/v1.3.4_test.go
@@ -2,7 +2,6 @@ package migrationnotes
 
 import (
 	"io/fs"
-	"io/ioutil"
 	"os"
 	"path"
 	"path/filepath"
@@ -18,15 +17,15 @@ func Test_needShow1_3_4(t *testing.T) {
 
 	dir1 := path.Join(root, "dir1")
 	require.NoError(t, os.Mkdir(dir1, fs.ModePerm))
-	ioutil.WriteFile(filepath.Join(dir1, "foo_gen.go"), nil, fs.ModePerm)
+	os.WriteFile(filepath.Join(dir1, "foo_gen.go"), nil, fs.ModePerm)
 
 	dir2 := path.Join(root, "dir2")
 	require.NoError(t, os.Mkdir(dir2, fs.ModePerm))
-	ioutil.WriteFile(filepath.Join(dir2, "foomodel.go"), nil, fs.ModePerm)
+	os.WriteFile(filepath.Join(dir2, "foomodel.go"), nil, fs.ModePerm)
 
 	dir3 := path.Join(root, "dir3")
 	require.NoError(t, os.Mkdir(dir3, fs.ModePerm))
-	ioutil.WriteFile(filepath.Join(dir3, "irrelevant.go"), nil, fs.ModePerm)
+	os.WriteFile(filepath.Join(dir3, "irrelevant.go"), nil, fs.ModePerm)
 
 	type args struct {
 		dir   string
diff --git a/tools/goctl/model/sql/gen/gen_test.go b/tools/goctl/model/sql/gen/gen_test.go
index 48d1060d..676e044c 100644
--- a/tools/goctl/model/sql/gen/gen_test.go
+++ b/tools/goctl/model/sql/gen/gen_test.go
@@ -3,7 +3,6 @@ package gen
 import (
 	"database/sql"
 	_ "embed"
-	"io/ioutil"
 	"os"
 	"path"
 	"path/filepath"
@@ -30,7 +29,7 @@ func TestCacheModel(t *testing.T) {
 	_ = Clean()
 
 	sqlFile := filepath.Join(pathx.MustTempDir(), "tmp.sql")
-	err := ioutil.WriteFile(sqlFile, []byte(source), 0o777)
+	err := os.WriteFile(sqlFile, []byte(source), 0o777)
 	assert.Nil(t, err)
 
 	dir := filepath.Join(pathx.MustTempDir(), "./testmodel")
@@ -65,7 +64,7 @@ func TestNamingModel(t *testing.T) {
 	_ = Clean()
 
 	sqlFile := filepath.Join(pathx.MustTempDir(), "tmp.sql")
-	err := ioutil.WriteFile(sqlFile, []byte(source), 0o777)
+	err := os.WriteFile(sqlFile, []byte(source), 0o777)
 	assert.Nil(t, err)
 
 	dir, _ := filepath.Abs("./testmodel")
@@ -103,7 +102,7 @@ func TestFolderName(t *testing.T) {
 	_ = Clean()
 
 	sqlFile := filepath.Join(pathx.MustTempDir(), "tmp.sql")
-	err := ioutil.WriteFile(sqlFile, []byte(source), 0o777)
+	err := os.WriteFile(sqlFile, []byte(source), 0o777)
 	assert.Nil(t, err)
 
 	dir, _ := filepath.Abs("./testmodel")
@@ -179,7 +178,7 @@ func Test_genPublicModel(t *testing.T) {
 	defer os.RemoveAll(dir)
 
 	modelFilename := filepath.Join(modelDir, "foo.sql")
-	err = ioutil.WriteFile(modelFilename, []byte(source), 0o777)
+	err = os.WriteFile(modelFilename, []byte(source), 0o777)
 	require.NoError(t, err)
 
 	g, err := NewDefaultGenerator(modelDir, &config.Config{
diff --git a/tools/goctl/model/sql/parser/parser_test.go b/tools/goctl/model/sql/parser/parser_test.go
index 0d205287..3f67a81d 100644
--- a/tools/goctl/model/sql/parser/parser_test.go
+++ b/tools/goctl/model/sql/parser/parser_test.go
@@ -2,7 +2,7 @@ package parser
 
 import (
 	_ "embed"
-	"io/ioutil"
+	"os"
 	"path/filepath"
 	"testing"
 
@@ -15,7 +15,7 @@ import (
 
 func TestParsePlainText(t *testing.T) {
 	sqlFile := filepath.Join(pathx.MustTempDir(), "tmp.sql")
-	err := ioutil.WriteFile(sqlFile, []byte("plain text"), 0o777)
+	err := os.WriteFile(sqlFile, []byte("plain text"), 0o777)
 	assert.Nil(t, err)
 
 	_, err = Parse(sqlFile, "go_zero", false)
@@ -24,7 +24,7 @@ func TestParsePlainText(t *testing.T) {
 
 func TestParseSelect(t *testing.T) {
 	sqlFile := filepath.Join(pathx.MustTempDir(), "tmp.sql")
-	err := ioutil.WriteFile(sqlFile, []byte("select * from user"), 0o777)
+	err := os.WriteFile(sqlFile, []byte("select * from user"), 0o777)
 	assert.Nil(t, err)
 
 	tables, err := Parse(sqlFile, "go_zero", false)
@@ -37,7 +37,7 @@ var user string
 
 func TestParseCreateTable(t *testing.T) {
 	sqlFile := filepath.Join(pathx.MustTempDir(), "tmp.sql")
-	err := ioutil.WriteFile(sqlFile, []byte(user), 0o777)
+	err := os.WriteFile(sqlFile, []byte(user), 0o777)
 	assert.Nil(t, err)
 
 	tables, err := Parse(sqlFile, "go_zero", false)
diff --git a/tools/goctl/pkg/parser/api/parser/analyzer_test.go b/tools/goctl/pkg/parser/api/parser/analyzer_test.go
index 28266f32..de4193b6 100644
--- a/tools/goctl/pkg/parser/api/parser/analyzer_test.go
+++ b/tools/goctl/pkg/parser/api/parser/analyzer_test.go
@@ -3,7 +3,7 @@ package parser
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
+	"os"
 	"path/filepath"
 	"strings"
 	"testing"
@@ -18,7 +18,7 @@ func Test_Parse(t *testing.T) {
 		assert.Nil(t, err)
 	})
 	t.Run("invalid", func(t *testing.T) {
-		data, err := ioutil.ReadFile("./testdata/invalid.api")
+		data, err := os.ReadFile("./testdata/invalid.api")
 		assert.NoError(t, err)
 		splits := bytes.Split(data, []byte("-----"))
 		var testFile []string
@@ -29,7 +29,7 @@ func Test_Parse(t *testing.T) {
 				continue
 			}
 			filename := filepath.Join(t.TempDir(), fmt.Sprintf("invalid%d.api", idx))
-			err := ioutil.WriteFile(filename, split, 0666)
+			err := os.WriteFile(filename, split, 0666)
 			assert.NoError(t, err)
 			testFile = append(testFile, filename)
 		}
diff --git a/tools/goctl/rpc/generator/genpb_test.go b/tools/goctl/rpc/generator/genpb_test.go
index 04f7fcf8..9ed939a4 100644
--- a/tools/goctl/rpc/generator/genpb_test.go
+++ b/tools/goctl/rpc/generator/genpb_test.go
@@ -1,7 +1,6 @@
 package generator
 
 import (
-	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -14,7 +13,7 @@ import (
 func Test_findPbFile(t *testing.T) {
 	dir := t.TempDir()
 	protoFile := filepath.Join(dir, "greet.proto")
-	err := ioutil.WriteFile(protoFile, []byte(`
+	err := os.WriteFile(protoFile, []byte(`
 syntax = "proto3";
 
 package greet;
diff --git a/tools/goctl/util/pathx/file_test.go b/tools/goctl/util/pathx/file_test.go
index 7505fe49..99833e39 100644
--- a/tools/goctl/util/pathx/file_test.go
+++ b/tools/goctl/util/pathx/file_test.go
@@ -1,7 +1,6 @@
 package pathx
 
 import (
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"testing"
@@ -23,7 +22,7 @@ func TestGetTemplateDir(t *testing.T) {
 			return
 		}
 		tempFile := filepath.Join(dir, "bar.txt")
-		err = ioutil.WriteFile(tempFile, []byte("foo"), os.ModePerm)
+		err = os.WriteFile(tempFile, []byte("foo"), os.ModePerm)
 		if err != nil {
 			return
 		}
@@ -79,7 +78,7 @@ func TestGetGoctlHome(t *testing.T) {
 	t.Run("goctl_is_file", func(t *testing.T) {
 		tmpFile := filepath.Join(t.TempDir(), "a.tmp")
 		backupTempFile := tmpFile + ".old"
-		err := ioutil.WriteFile(tmpFile, nil, 0o666)
+		err := os.WriteFile(tmpFile, nil, 0o666)
 		if err != nil {
 			return
 		}
diff --git a/tools/goctl/util/pathx/path_test.go b/tools/goctl/util/pathx/path_test.go
index 67b5a4b5..0b7c2a82 100644
--- a/tools/goctl/util/pathx/path_test.go
+++ b/tools/goctl/util/pathx/path_test.go
@@ -1,7 +1,6 @@
 package pathx
 
 import (
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"testing"
@@ -10,9 +9,9 @@ import (
 )
 
 func TestReadLink(t *testing.T) {
-	dir, err := ioutil.TempDir("", "go-zero")
+	dir, err := os.CreateTemp("", "go-zero")
 	assert.Nil(t, err)
-	symLink := filepath.Join(dir, "test")
+	symLink := filepath.Join(dir.Name(), "test")
 	pwd, err := os.Getwd()
 	assertError(err, t)