zhangb 3 tahun lalu
induk
melakukan
9b3b5c0fca

+ 2 - 2
example/sample/main.go

@@ -2,8 +2,8 @@ package main
 
 import (
 	"github.com/goflyfox/gtoken/gtoken"
-	"github.com/gogf/gf/frame/g"
-	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/net/ghttp"
 )
 
 var TestServerName string

+ 16 - 12
example/sample/test/api_admin_test.go

@@ -1,10 +1,10 @@
 package test
 
 import (
+	"context"
 	"encoding/json"
 	"github.com/goflyfox/gtoken/gtoken"
-	"github.com/gogf/gf/frame/g"
-	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/v2/frame/g"
 	"testing"
 )
 
@@ -20,7 +20,7 @@ var (
 func TestAdminSystemUser(t *testing.T) {
 	// 未登录
 	t.Log("1. not login and visit user")
-	if r, e := g.Client().Post(TestAdminURL+"/system/user", "username="+AdminUsername); e != nil {
+	if r, e := g.Client().Post(context.TODO(), TestAdminURL+"/system/user", "username="+AdminUsername); e != nil {
 		t.Error("error:", e)
 	} else {
 		defer r.Close()
@@ -70,7 +70,7 @@ func TestAdminSystemUser(t *testing.T) {
 func TestAdminUserLoginFail(t *testing.T) {
 	// 登录失败
 	t.Log("1. login fail ")
-	if r, e := g.Client().Post(TestAdminURL+"/login", "username=&passwd="); e != nil {
+	if r, e := g.Client().Post(context.TODO(), TestAdminURL+"/login", "username=&passwd="); e != nil {
 		t.Error("error:", e)
 	} else {
 		defer r.Close()
@@ -93,7 +93,7 @@ func TestAdminUserLoginFail(t *testing.T) {
 func TestAdminExclude(t *testing.T) {
 	// 未登录可以访问
 	t.Log("1. exclude user info")
-	if r, e := g.Client().Post(TestAdminURL+"/system/user/info", "username="+AdminUsername); e != nil {
+	if r, e := g.Client().Post(context.TODO(), TestAdminURL+"/system/user/info", "username="+AdminUsername); e != nil {
 		t.Error("error:", e)
 	} else {
 		defer r.Close()
@@ -111,7 +111,7 @@ func TestAdminExclude(t *testing.T) {
 		}
 	}
 
-	if r, e := g.Client().Post(TestAdminURL+"/user/info", "username="+AdminUsername); e != nil {
+	if r, e := g.Client().Post(context.TODO(), TestAdminURL+"/user/info", "username="+AdminUsername); e != nil {
 		t.Error("error:", e)
 	} else {
 		defer r.Close()
@@ -158,7 +158,7 @@ func TestAdminLogout(t *testing.T) {
 func TestAdminMultiLogin(t *testing.T) {
 	t.Log(" TestMultiLogin start... ")
 	var token1, token2 string
-	if r, e := g.Client().Post(TestAdminURL+"/login", "username="+AdminUsername+"&passwd=123456"); e != nil {
+	if r, e := g.Client().Post(context.TODO(), TestAdminURL+"/login", "username="+AdminUsername+"&passwd=123456"); e != nil {
 		t.Error("error:", e)
 	} else {
 		defer r.Close()
@@ -180,7 +180,7 @@ func TestAdminMultiLogin(t *testing.T) {
 	}
 	t.Log("token1:" + token1)
 
-	if r, e := g.Client().Post(TestAdminURL+"/login", "username="+AdminUsername+"&passwd=123456"); e != nil {
+	if r, e := g.Client().Post(context.TODO(), TestAdminURL+"/login", "username="+AdminUsername+"&passwd=123456"); e != nil {
 		t.Error("error:", e)
 	} else {
 		defer r.Close()
@@ -203,7 +203,11 @@ func TestAdminMultiLogin(t *testing.T) {
 
 	t.Log("token2:" + token2)
 
-	if g.Config().GetBool("gToken.MultiLogin") {
+	gVar, err := g.Cfg().Get(context.TODO(), "gToken.MultiLogin")
+	if err != nil {
+		t.Error("error:", err)
+	}
+	if gVar.Bool() {
 		if token1 != token2 {
 			t.Error("error:", "token not same ")
 		}
@@ -216,9 +220,9 @@ func TestAdminMultiLogin(t *testing.T) {
 }
 
 func PostAdmin(t *testing.T, urlPath string, data ...interface{}) gtoken.Resp {
-	client := ghttp.NewClient()
+	client := g.Client()
 	client.SetHeader("Authorization", "Bearer "+getAdminToken(t))
-	content := client.RequestContent("POST", TestAdminURL+urlPath, data...)
+	content := client.RequestContent(context.TODO(), "POST", TestAdminURL+urlPath, data...)
 	var respData gtoken.Resp
 	err := json.Unmarshal([]byte(content), &respData)
 	if err != nil {
@@ -232,7 +236,7 @@ func getAdminToken(t *testing.T) string {
 		return TokenAdmin[AdminUsername]
 	}
 
-	if r, e := g.Client().Post(TestAdminURL+"/login", "username="+AdminUsername+"&passwd=123456"); e != nil {
+	if r, e := g.Client().Post(context.TODO(), TestAdminURL+"/login", "username="+AdminUsername+"&passwd=123456"); e != nil {
 		t.Error("error:", e)
 	} else {
 		defer r.Close()

+ 17 - 13
example/sample/test/api_test.go

@@ -1,12 +1,12 @@
 package test
 
 import (
+	"context"
 	"encoding/json"
 	"fmt"
 	"github.com/goflyfox/gtoken/example/sample/test/server"
 	"github.com/goflyfox/gtoken/gtoken"
-	"github.com/gogf/gf/frame/g"
-	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/v2/frame/g"
 	"os"
 	"testing"
 )
@@ -39,7 +39,7 @@ func TestMain(m *testing.M) {
 
 func TestHello(t *testing.T) {
 	t.Log("visit hello and no auth")
-	if r, e := g.Client().Post(TestURL+"/hello", "username="+Username); e != nil {
+	if r, e := g.Client().Post(context.TODO(), TestURL+"/hello", "username="+Username); e != nil {
 		t.Error("error:", e)
 	} else {
 		defer r.Close()
@@ -61,7 +61,7 @@ func TestHello(t *testing.T) {
 func TestSystemUser(t *testing.T) {
 	// 未登录
 	t.Log("1. not login and visit user")
-	if r, e := g.Client().Post(TestURL+"/system/user", "username="+Username); e != nil {
+	if r, e := g.Client().Post(context.TODO(), TestURL+"/system/user", "username="+Username); e != nil {
 		t.Error("error:", e)
 	} else {
 		defer r.Close()
@@ -120,7 +120,7 @@ func TestSystemUser(t *testing.T) {
 func TestUserLoginFail(t *testing.T) {
 	// 登录失败
 	t.Log("1. login fail ")
-	if r, e := g.Client().Post(TestURL+"/login", "username=&passwd="); e != nil {
+	if r, e := g.Client().Post(context.TODO(), TestURL+"/login", "username=&passwd="); e != nil {
 		t.Error("error:", e)
 	} else {
 		defer r.Close()
@@ -143,7 +143,7 @@ func TestUserLoginFail(t *testing.T) {
 func TestExclude(t *testing.T) {
 	// 未登录可以访问
 	t.Log("1. exclude user info")
-	if r, e := g.Client().Post(TestURL+"/system/user/info", "username="+Username); e != nil {
+	if r, e := g.Client().Post(context.TODO(), TestURL+"/system/user/info", "username="+Username); e != nil {
 		t.Error("error:", e)
 	} else {
 		defer r.Close()
@@ -161,7 +161,7 @@ func TestExclude(t *testing.T) {
 		}
 	}
 
-	if r, e := g.Client().Post(TestURL+"/user/info", "username="+Username); e != nil {
+	if r, e := g.Client().Post(context.TODO(), TestURL+"/user/info", "username="+Username); e != nil {
 		t.Error("error:", e)
 	} else {
 		defer r.Close()
@@ -232,7 +232,7 @@ func TestLogout(t *testing.T) {
 func TestMultiLogin(t *testing.T) {
 	t.Log(" TestMultiLogin start... ")
 	var token1, token2 string
-	if r, e := g.Client().Post(TestURL+"/login", "username="+Username+"&passwd=123456"); e != nil {
+	if r, e := g.Client().Post(context.TODO(), TestURL+"/login", "username="+Username+"&passwd=123456"); e != nil {
 		t.Error("error:", e)
 	} else {
 		defer r.Close()
@@ -254,7 +254,7 @@ func TestMultiLogin(t *testing.T) {
 	}
 	t.Log("token1:" + token1)
 
-	if r, e := g.Client().Post(TestURL+"/login", "username="+Username+"&passwd=123456"); e != nil {
+	if r, e := g.Client().Post(context.TODO(), TestURL+"/login", "username="+Username+"&passwd=123456"); e != nil {
 		t.Error("error:", e)
 	} else {
 		defer r.Close()
@@ -277,7 +277,11 @@ func TestMultiLogin(t *testing.T) {
 
 	t.Log("token2:" + token2)
 
-	if g.Config().GetBool("gToken.MultiLogin") {
+	gVar, err := g.Cfg().Get(context.TODO(), "gToken.MultiLogin")
+	if err != nil {
+		t.Error("error:", err)
+	}
+	if gVar.Bool() {
 		if token1 != token2 {
 			t.Error("error:", "token not same ")
 		}
@@ -289,9 +293,9 @@ func TestMultiLogin(t *testing.T) {
 }
 
 func Post(t *testing.T, urlPath string, data ...interface{}) gtoken.Resp {
-	client := ghttp.NewClient()
+	client := g.Client()
 	client.SetHeader("Authorization", "Bearer "+getToken(t))
-	content := client.RequestContent("POST", TestURL+urlPath, data...)
+	content := client.RequestContent(context.TODO(), "POST", TestURL+urlPath, data...)
 	var respData gtoken.Resp
 	err := json.Unmarshal([]byte(content), &respData)
 	if err != nil {
@@ -305,7 +309,7 @@ func getToken(t *testing.T) string {
 		return Token[Username]
 	}
 
-	if r, e := g.Client().Post(TestURL+"/login", "username="+Username+"&passwd=123456"); e != nil {
+	if r, e := g.Client().Post(context.TODO(), TestURL+"/login", "username="+Username+"&passwd=123456"); e != nil {
 		t.Error("error:", e)
 	} else {
 		defer r.Close()

+ 22 - 15
example/sample/test/cache_test.go

@@ -1,31 +1,33 @@
 package test
 
 import (
+	"context"
 	"github.com/goflyfox/gtoken/gtoken"
-	"github.com/gogf/gf/encoding/gjson"
-	"github.com/gogf/gf/frame/g"
-	"github.com/gogf/gf/os/gcache"
-	"github.com/gogf/gf/util/gconv"
+	"github.com/gogf/gf/v2/encoding/gjson"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/os/gcache"
+	"github.com/gogf/gf/v2/util/gconv"
 	"testing"
 	"time"
 )
 
 func TestGCache(t *testing.T) {
 	t.Log("gcache test ")
+	ctx := context.TODO()
 	userKey := "123123"
-	gcache.Set(userKey, "1", 10000)
+	gcache.Set(ctx, userKey, "1", 10000)
 
-	value, err := gcache.Get(userKey)
+	value, err := gcache.Get(ctx, userKey)
 	if err != nil {
 		t.Error("cache set error," + err.Error())
 	}
-	if value.(string) == userKey {
+	if value.String() == userKey {
 		t.Error("cache get error")
 	}
 
-	gcache.Remove(userKey)
+	gcache.Remove(ctx, userKey)
 
-	value, err = gcache.Get(userKey)
+	value, err = gcache.Get(ctx, userKey)
 	if err != nil {
 		t.Error("cache set error," + err.Error())
 	}
@@ -36,20 +38,25 @@ func TestGCache(t *testing.T) {
 }
 
 func TestRedisCache(t *testing.T) {
-	if g.Config().GetInt8("gtoken.cache-mode") != gtoken.CacheModeRedis {
+	ctx := context.TODO()
+	gVar, err := g.Cfg().Get(ctx, "gToken.CacheMode")
+	if err != nil {
+		t.Error("error:", err)
+	}
+	if gVar.Int() != gtoken.CacheModeRedis {
 		t.Log("redis cache not test ")
 		return
 	}
 
 	t.Log("redis cache test ")
 	userKey := "test:a"
-	_, err := g.Redis().Do("SETEX", userKey, 10000, "1")
+	_, err = g.Redis().Do(ctx, "SETEX", userKey, 10000, "1")
 	if err != nil {
 		t.Error("cache set error," + err.Error())
 	}
 
 	time.Sleep(1 * time.Second)
-	ttl, err2 := g.Redis().Do("TTL", userKey)
+	ttl, err2 := g.Redis().Do(ctx, "TTL", userKey)
 	if err2 != nil {
 		t.Error("cache ttl error," + err.Error())
 	}
@@ -58,7 +65,7 @@ func TestRedisCache(t *testing.T) {
 		t.Error("cache ttl error, ttl:" + gconv.String(ttl))
 	}
 
-	data, err3 := g.Redis().Do("GET", userKey)
+	data, err3 := g.Redis().Do(ctx, "GET", userKey)
 	if err3 != nil {
 		t.Error("cache get error," + err.Error())
 	}
@@ -67,8 +74,8 @@ func TestRedisCache(t *testing.T) {
 		t.Error("cache get error, data:" + gconv.String(data))
 	}
 
-	g.Redis().Do("DEL", userKey)
-	data, err4 := g.Redis().Do("GET", userKey)
+	g.Redis().Do(ctx, "DEL", userKey)
+	data, err4 := g.Redis().Do(ctx, "GET", userKey)
 	if err4 != nil {
 		t.Error("cache del get error," + err.Error())
 	}

+ 2 - 2
example/sample/test/server/server.go

@@ -2,8 +2,8 @@ package server
 
 import (
 	"github.com/goflyfox/gtoken/gtoken"
-	"github.com/gogf/gf/frame/g"
-	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/net/ghttp"
 )
 
 var TestServerName string

+ 2 - 2
example/sample/test/time_test.go

@@ -1,8 +1,8 @@
 package test
 
 import (
-	"github.com/gogf/gf/os/gtime"
-	"github.com/gogf/gf/util/gconv"
+	"github.com/gogf/gf/v2/os/gtime"
+	"github.com/gogf/gf/v2/util/gconv"
 	"testing"
 	"time"
 )

+ 24 - 14
example/sample1/main.go

@@ -1,9 +1,11 @@
 package main
 
 import (
+	"context"
 	"github.com/goflyfox/gtoken/gtoken"
-	"github.com/gogf/gf/frame/g"
-	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/v2/container/gvar"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/net/ghttp"
 )
 
 var TestServerName string
@@ -11,13 +13,14 @@ var TestServerName string
 //var TestServerName string = "gtoken"
 
 func main() {
-	g.Log().Info("########service start...")
+	ctx := context.TODO()
+	g.Log().Info(ctx, "########service start...")
 
 	g.Cfg().SetPath("example/sample1")
 	s := g.Server(TestServerName)
 	initRouter(s)
 
-	g.Log().Info("########service finish.")
+	g.Log().Info(ctx, "########service finish.")
 	s.Run()
 }
 
@@ -27,6 +30,8 @@ var gfToken *gtoken.GfToken
 统一路由注册
 */
 func initRouter(s *ghttp.Server) {
+	ctx := context.TODO()
+
 	s.Group("/", func(group *ghttp.RouterGroup) {
 		group.Middleware(CORS)
 
@@ -54,14 +59,14 @@ func initRouter(s *ghttp.Server) {
 	gfToken = &gtoken.GfToken{
 		ServerName: TestServerName,
 		//Timeout:         10 * 1000,
-		CacheMode:        g.Cfg().GetInt8("gToken.CacheMode"),
-		CacheKey:         g.Cfg().GetString("gToken.CacheKey"),
-		Timeout:          g.Cfg().GetInt("gToken.Timeout"),
-		MaxRefresh:       g.Cfg().GetInt("gToken.MaxRefresh"),
-		TokenDelimiter:   g.Cfg().GetString("gToken.TokenDelimiter"),
-		EncryptKey:       g.Cfg().GetBytes("gToken.EncryptKey"),
-		AuthFailMsg:      g.Cfg().GetString("gToken.AuthFailMsg"),
-		MultiLogin:       g.Config().GetBool("gToken.MultiLogin"),
+		CacheMode:        CfgGet(ctx, "gToken.CacheMode").Int8(),
+		CacheKey:         CfgGet(ctx, "gToken.CacheKey").String(),
+		Timeout:          CfgGet(ctx, "gToken.Timeout").Int(),
+		MaxRefresh:       CfgGet(ctx, "gToken.MaxRefresh").Int(),
+		TokenDelimiter:   CfgGet(ctx, "gToken.TokenDelimiter").String(),
+		EncryptKey:       CfgGet(ctx, "gToken.EncryptKey").Bytes(),
+		AuthFailMsg:      CfgGet(ctx, "gToken.AuthFailMsg").String(),
+		MultiLogin:       CfgGet(ctx, "gToken.MultiLogin").Bool(),
 		LoginPath:        "/login",
 		LoginBeforeFunc:  loginFunc,
 		LogoutPath:       "/user/logout",
@@ -75,9 +80,14 @@ func initRouter(s *ghttp.Server) {
 	}
 }
 
+func CfgGet(ctx context.Context, name string) *gvar.Var {
+	gVar, _ := g.Config().Get(ctx, name)
+	return gVar
+}
+
 func Login(r *ghttp.Request) (string, interface{}) {
-	username := r.GetString("username")
-	passwd := r.GetString("passwd")
+	username := r.Get("username").String()
+	passwd := r.Get("passwd").String()
 
 	if username == "" || passwd == "" {
 		r.Response.WriteJson(gtoken.Fail("账号或密码错误."))

+ 2 - 2
example/sample1/test/api_test.go

@@ -5,8 +5,8 @@ import (
 	"fmt"
 	"github.com/goflyfox/gtoken/example/sample1/test/server1"
 	"github.com/goflyfox/gtoken/gtoken"
-	"github.com/gogf/gf/frame/g"
-	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/net/ghttp"
 	"os"
 	"testing"
 )

+ 2 - 2
example/sample1/test/server1/server.go

@@ -2,8 +2,8 @@ package server1
 
 import (
 	"github.com/goflyfox/gtoken/gtoken"
-	"github.com/gogf/gf/frame/g"
-	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/net/ghttp"
 )
 
 var TestServerName string

+ 1 - 1
go.mod

@@ -1,5 +1,5 @@
 module github.com/goflyfox/gtoken
 
-require github.com/gogf/gf v1.16.6
+require github.com/gogf/gf/v2 v2.0.0-beta.0.20211218044333-ad48dc025b0b
 
 go 1.13

+ 18 - 16
gtoken/gtoken.go

@@ -1,17 +1,18 @@
 package gtoken
 
 import (
+	"context"
 	"errors"
 	"fmt"
-	"github.com/gogf/gf/crypto/gaes"
-	"github.com/gogf/gf/crypto/gmd5"
-	"github.com/gogf/gf/encoding/gbase64"
-	"github.com/gogf/gf/frame/g"
-	"github.com/gogf/gf/net/ghttp"
-	"github.com/gogf/gf/os/gtime"
-	"github.com/gogf/gf/text/gstr"
-	"github.com/gogf/gf/util/gconv"
-	"github.com/gogf/gf/util/grand"
+	"github.com/gogf/gf/v2/crypto/gaes"
+	"github.com/gogf/gf/v2/crypto/gmd5"
+	"github.com/gogf/gf/v2/encoding/gbase64"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/net/ghttp"
+	"github.com/gogf/gf/v2/os/gtime"
+	"github.com/gogf/gf/v2/text/gstr"
+	"github.com/gogf/gf/v2/util/gconv"
+	"github.com/gogf/gf/v2/util/grand"
 	"net/http"
 	"strings"
 )
@@ -201,23 +202,23 @@ func (m *GfToken) InitConfig() bool {
 }
 
 // Start 启动
-func (m *GfToken) Start() error {
+func (m *GfToken) Start(ctx context.Context) error {
 	if !m.InitConfig() {
 		return errors.New("InitConfig fail")
 	}
-	g.Log().Info("[GToken][params:" + m.String() + "]start... ")
+	g.Log().Info(ctx, "[GToken][params:"+m.String()+"]start... ")
 
 	s := g.Server(m.ServerName)
 
 	// 缓存模式
 	if m.CacheMode > CacheModeRedis {
-		g.Log().Error("[GToken]CacheMode set error")
+		g.Log().Error(ctx, "[GToken]CacheMode set error")
 		return errors.New("CacheMode set error")
 	}
 
 	// 认证拦截器
 	if m.AuthPaths == nil {
-		g.Log().Error("[GToken]AuthPaths not set")
+		g.Log().Error(ctx, "[GToken]AuthPaths not set")
 		return errors.New("AuthPaths not set")
 	}
 
@@ -236,14 +237,14 @@ func (m *GfToken) Start() error {
 
 	// 登录
 	if m.LoginPath == "" || m.LoginBeforeFunc == nil {
-		g.Log().Error("[GToken]LoginPath or LoginBeforeFunc not set")
+		g.Log().Error(ctx, "[GToken]LoginPath or LoginBeforeFunc not set")
 		return errors.New("LoginPath or LoginBeforeFunc not set")
 	}
 	s.BindHandler(m.LoginPath, m.Login)
 
 	// 登出
 	if m.LogoutPath == "" {
-		g.Log().Error("[GToken]LogoutPath not set")
+		g.Log().Error(ctx, "[GToken]LogoutPath not set")
 		return errors.New("LogoutPath not set")
 	}
 	s.BindHandler(m.LogoutPath, m.Logout)
@@ -270,9 +271,10 @@ func (m *GfToken) GetTokenData(r *ghttp.Request) Resp {
 
 // Login 登录
 func (m *GfToken) Login(r *ghttp.Request) {
+	ctx := r.Context()
 	userKey, data := m.LoginBeforeFunc(r)
 	if userKey == "" {
-		g.Log().Error("[GToken]Login userKey is empty")
+		g.Log().Error(ctx, "[GToken]Login userKey is empty")
 		return
 	}
 

+ 4 - 4
gtoken/gtoken_cache.go

@@ -1,10 +1,10 @@
 package gtoken
 
 import (
-	"github.com/gogf/gf/encoding/gjson"
-	"github.com/gogf/gf/frame/g"
-	"github.com/gogf/gf/os/gcache"
-	"github.com/gogf/gf/util/gconv"
+	"github.com/gogf/gf/v2/encoding/gjson"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/os/gcache"
+	"github.com/gogf/gf/v2/util/gconv"
 	"time"
 )
 

+ 2 - 2
gtoken/gtoken_group.go

@@ -2,8 +2,8 @@ package gtoken
 
 import (
 	"errors"
-	"github.com/gogf/gf/frame/g"
-	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/net/ghttp"
 )
 
 // Middleware 绑定group

+ 1 - 1
gtoken/gtoken_resp.go

@@ -2,7 +2,7 @@ package gtoken
 
 import (
 	"encoding/json"
-	"github.com/gogf/gf/util/gconv"
+	"github.com/gogf/gf/v2/util/gconv"
 )
 
 const (

+ 1 - 1
gtoken/gtoken_test.go

@@ -2,7 +2,7 @@ package gtoken_test
 
 import (
 	"github.com/goflyfox/gtoken/gtoken"
-	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/v2/frame/g"
 	"testing"
 )