updates #207
							
								
								
									
										9
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								go.mod
									
									
									
									
									
								
							| @@ -3,8 +3,17 @@ module go.unistack.org/micro/v3 | ||||
| go 1.19 | ||||
|  | ||||
| require ( | ||||
| 	github.com/DATA-DOG/go-sqlmock v1.5.0 | ||||
| 	github.com/google/uuid v1.3.0 | ||||
| 	github.com/imdario/mergo v0.3.15 | ||||
| 	github.com/patrickmn/go-cache v2.1.0+incompatible | ||||
| 	github.com/silas/dag v0.0.0-20211117232152-9d50aa809f35 | ||||
| 	golang.org/x/sync v0.3.0 | ||||
| 	google.golang.org/grpc v1.57.0 | ||||
| 	google.golang.org/protobuf v1.31.0 | ||||
| ) | ||||
|  | ||||
| require ( | ||||
| 	github.com/golang/protobuf v1.5.3 // indirect | ||||
| 	google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect | ||||
| ) | ||||
|   | ||||
							
								
								
									
										21
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								go.sum
									
									
									
									
									
								
							| @@ -1,3 +1,10 @@ | ||||
| github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= | ||||
| github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= | ||||
| github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | ||||
| github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= | ||||
| github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | ||||
| github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= | ||||
| github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= | ||||
| github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||
| github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= | ||||
| @@ -6,6 +13,20 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR | ||||
| github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= | ||||
| github.com/silas/dag v0.0.0-20211117232152-9d50aa809f35 h1:4mohWoM/UGg1BvFFiqSPRl5uwJY3rVV0HQX0ETqauqQ= | ||||
| github.com/silas/dag v0.0.0-20211117232152-9d50aa809f35/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I= | ||||
| golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= | ||||
| golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= | ||||
| golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= | ||||
| golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= | ||||
| golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= | ||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= | ||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= | ||||
| google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= | ||||
| google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= | ||||
| google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | ||||
| google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||
| google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= | ||||
| google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
|   | ||||
| @@ -5,8 +5,32 @@ import ( | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestTrieBackwards(t *testing.T) { | ||||
| 	_ = &Trie{} | ||||
| func TestTrieRPC(t *testing.T) { | ||||
| 	var err error | ||||
| 	type handler struct { | ||||
| 		name string | ||||
| 	} | ||||
| 	tr := NewTrie() | ||||
| 	if err = tr.Insert([]string{"helloworld"}, "Call", &handler{name: "helloworld.Call"}); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if err = tr.Insert([]string{"helloworld"}, "Stream", &handler{name: "helloworld.Stream"}); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	h, _, err := tr.Search("helloworld", "Call") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error %v", err) | ||||
| 	} | ||||
| 	if h.(*handler).name != "helloworld.Call" { | ||||
| 		t.Fatalf("invalid handler %v", h) | ||||
| 	} | ||||
| 	h, _, err = tr.Search("helloworld", "Stream") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error %v", err) | ||||
| 	} | ||||
| 	if h.(*handler).name != "helloworld.Stream" { | ||||
| 		t.Fatalf("invalid handler %v", h) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTrieWildcardPathPrefix(t *testing.T) { | ||||
|   | ||||
							
								
								
									
										505
									
								
								util/test/test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										505
									
								
								util/test/test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,505 @@ | ||||
| package test | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"database/sql/driver" | ||||
| 	"encoding/csv" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"path/filepath" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	sqlmock "github.com/DATA-DOG/go-sqlmock" | ||||
| 	"go.unistack.org/micro/v3/client" | ||||
| 	"go.unistack.org/micro/v3/codec" | ||||
| 	"go.unistack.org/micro/v3/errors" | ||||
| 	"go.unistack.org/micro/v3/metadata" | ||||
| 	"golang.org/x/sync/errgroup" | ||||
| 	"google.golang.org/grpc/status" | ||||
| 	"google.golang.org/protobuf/proto" | ||||
| ) | ||||
|  | ||||
| var ErrUnknownContentType = fmt.Errorf("unknown content type") | ||||
|  | ||||
| type Extension struct { | ||||
| 	Ext []string | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	// ExtToTypes map file extension to content type | ||||
| 	ExtToTypes = map[string][]string{ | ||||
| 		"json":  {"application/json", "application/grpc+json"}, | ||||
| 		"yaml":  {"application/yaml", "application/yml", "text/yaml", "text/yml"}, | ||||
| 		"yml":   {"application/yaml", "application/yml", "text/yaml", "text/yml"}, | ||||
| 		"proto": {"application/grpc", "application/grpc+proto", "application/proto"}, | ||||
| 	} | ||||
| 	// DefaultExts specifies default file extensions to load data | ||||
| 	DefaultExts = []string{"csv", "json", "yaml", "yml", "proto"} | ||||
| 	// Codecs map to detect codec for test file or request content type | ||||
| 	Codecs map[string]codec.Codec | ||||
|  | ||||
| 	// ResponseCompareFunc used to compare actual response with test case data | ||||
| 	ResponseCompareFunc = func(expectRsp []byte, testRsp interface{}, expectCodec codec.Codec, testCodec codec.Codec) error { | ||||
| 		var err error | ||||
|  | ||||
| 		expectMap := make(map[string]interface{}) | ||||
| 		if err = expectCodec.Unmarshal(expectRsp, &expectMap); err != nil { | ||||
| 			return fmt.Errorf("failed to unmarshal err: %w", err) | ||||
| 		} | ||||
|  | ||||
| 		testMap := make(map[string]interface{}) | ||||
| 		switch v := testRsp.(type) { | ||||
| 		case *codec.Frame: | ||||
| 			if err = testCodec.Unmarshal(v.Data, &testMap); err != nil { | ||||
| 				return fmt.Errorf("failed to unmarshal err: %w", err) | ||||
| 			} | ||||
| 		case *errors.Error: | ||||
| 			if err = expectCodec.Unmarshal([]byte(v.Error()), &testMap); err != nil { | ||||
| 				return fmt.Errorf("failed to unmarshal err: %w", err) | ||||
| 			} | ||||
| 		case error: | ||||
| 			st, ok := status.FromError(v) | ||||
| 			if !ok { | ||||
| 				return v | ||||
| 			} | ||||
| 			me := errors.Parse(st.Message()) | ||||
| 			if me.Code != 0 { | ||||
| 				if err = expectCodec.Unmarshal([]byte(me.Error()), &testMap); err != nil { | ||||
| 					return fmt.Errorf("failed to unmarshal err: %w", err) | ||||
| 				} | ||||
| 				break | ||||
| 			} | ||||
| 			for _, se := range st.Details() { | ||||
| 				switch ne := se.(type) { | ||||
| 				case proto.Message: | ||||
| 					buf, err := testCodec.Marshal(ne) | ||||
| 					if err != nil { | ||||
| 						return fmt.Errorf("failed to marshal err: %w", err) | ||||
| 					} | ||||
| 					if err = testCodec.Unmarshal(buf, &testMap); err != nil { | ||||
| 						return fmt.Errorf("failed to unmarshal err: %w", err) | ||||
| 					} | ||||
| 				default: | ||||
| 					return st.Err() | ||||
| 				} | ||||
| 			} | ||||
| 		case interface{ GRPCStatus() *status.Status }: | ||||
| 			st := v.GRPCStatus() | ||||
| 			me := errors.Parse(st.Message()) | ||||
| 			if me.Code != 0 { | ||||
| 				if err = expectCodec.Unmarshal([]byte(me.Error()), &testMap); err != nil { | ||||
| 					return fmt.Errorf("failed to unmarshal err: %w", err) | ||||
| 				} | ||||
| 				break | ||||
| 			} | ||||
| 		case *status.Status: | ||||
| 			me := errors.Parse(v.Message()) | ||||
| 			if me.Code != 0 { | ||||
| 				if err = expectCodec.Unmarshal([]byte(me.Error()), &testMap); err != nil { | ||||
| 					return fmt.Errorf("failed to unmarshal err: %w", err) | ||||
| 				} | ||||
| 				break | ||||
| 			} | ||||
| 			for _, se := range v.Details() { | ||||
| 				switch ne := se.(type) { | ||||
| 				case proto.Message: | ||||
| 					buf, err := testCodec.Marshal(ne) | ||||
| 					if err != nil { | ||||
| 						return fmt.Errorf("failed to marshal err: %w", err) | ||||
| 					} | ||||
| 					if err = testCodec.Unmarshal(buf, &testMap); err != nil { | ||||
| 						return fmt.Errorf("failed to unmarshal err: %w", err) | ||||
| 					} | ||||
| 				default: | ||||
| 					return v.Err() | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if !reflect.DeepEqual(expectMap, testMap) { | ||||
| 			return fmt.Errorf("test: %s != rsp: %s", expectMap, testMap) | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| func FromCSVString(columns []*sqlmock.Column, rows *sqlmock.Rows, s string) *sqlmock.Rows { | ||||
| 	res := strings.NewReader(strings.TrimSpace(s)) | ||||
| 	csvReader := csv.NewReader(res) | ||||
|  | ||||
| 	for { | ||||
| 		res, err := csvReader.Read() | ||||
| 		if err != nil || res == nil { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		var row []driver.Value | ||||
| 		for i, v := range res { | ||||
| 			item := CSVColumnParser(strings.TrimSpace(v)) | ||||
| 			if null, nullOk := columns[i].IsNullable(); null && nullOk && item == nil { | ||||
| 				row = append(row, nil) | ||||
| 			} else { | ||||
| 				row = append(row, item) | ||||
| 			} | ||||
|  | ||||
| 		} | ||||
| 		rows = rows.AddRow(row...) | ||||
| 	} | ||||
|  | ||||
| 	return rows | ||||
| } | ||||
|  | ||||
| func CSVColumnParser(s string) []byte { | ||||
| 	switch { | ||||
| 	case strings.ToLower(s) == "null": | ||||
| 		return nil | ||||
| 	case s == "": | ||||
| 		return nil | ||||
| 	} | ||||
| 	return []byte(s) | ||||
| } | ||||
|  | ||||
| func NewResponseFromFile(rspfile string) (*codec.Frame, error) { | ||||
| 	rspbuf, err := os.ReadFile(rspfile) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &codec.Frame{Data: rspbuf}, nil | ||||
| } | ||||
|  | ||||
| func getCodec(codecs map[string]codec.Codec, ext string) (codec.Codec, error) { | ||||
| 	var c codec.Codec | ||||
| 	if cts, ok := ExtToTypes[ext]; ok { | ||||
| 		for _, t := range cts { | ||||
| 			if c, ok = codecs[t]; ok { | ||||
| 				return c, nil | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil, ErrUnknownContentType | ||||
| } | ||||
|  | ||||
| func getContentType(codecs map[string]codec.Codec, ext string) (string, error) { | ||||
| 	if cts, ok := ExtToTypes[ext]; ok { | ||||
| 		for _, t := range cts { | ||||
| 			if _, ok = codecs[t]; ok { | ||||
| 				return t, nil | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return "", ErrUnknownContentType | ||||
| } | ||||
|  | ||||
| func getExt(name string) string { | ||||
| 	ext := filepath.Ext(name) | ||||
| 	if len(ext) > 0 && ext[0] == '.' { | ||||
| 		ext = ext[1:] | ||||
| 	} | ||||
| 	return ext | ||||
| } | ||||
|  | ||||
| func getNameWithoutExt(name string) string { | ||||
| 	return strings.TrimSuffix(name, filepath.Ext(name)) | ||||
| } | ||||
|  | ||||
| func NewRequestFromFile(c client.Client, reqfile string) (client.Request, error) { | ||||
| 	reqbuf, err := os.ReadFile(reqfile) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	endpoint := path.Base(path.Dir(reqfile)) | ||||
| 	if idx := strings.Index(endpoint, "_"); idx > 0 { | ||||
| 		endpoint = endpoint[idx+1:] | ||||
| 	} | ||||
| 	ext := getExt(reqfile) | ||||
|  | ||||
| 	ct, err := getContentType(c.Options().Codecs, ext) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	req := c.NewRequest("test", endpoint, &codec.Frame{Data: reqbuf}, client.RequestContentType(ct)) | ||||
|  | ||||
| 	return req, nil | ||||
| } | ||||
|  | ||||
| func SQLFromFile(m sqlmock.Sqlmock, name string) error { | ||||
| 	fp, err := os.Open(name) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer fp.Close() | ||||
| 	return SQLFromReader(m, fp) | ||||
| } | ||||
|  | ||||
| func SQLFromBytes(m sqlmock.Sqlmock, buf []byte) error { | ||||
| 	return SQLFromReader(m, bytes.NewReader(buf)) | ||||
| } | ||||
|  | ||||
| func SQLFromString(m sqlmock.Sqlmock, buf string) error { | ||||
| 	return SQLFromReader(m, strings.NewReader(buf)) | ||||
| } | ||||
|  | ||||
| func SQLFromReader(m sqlmock.Sqlmock, r io.Reader) error { | ||||
| 	var rows *sqlmock.Rows | ||||
| 	var exp *sqlmock.ExpectedQuery | ||||
| 	var columns []*sqlmock.Column | ||||
|  | ||||
| 	br := bufio.NewReader(r) | ||||
|  | ||||
| 	for { | ||||
| 		s, err := br.ReadString('\n') | ||||
| 		if err != nil && err != io.EOF { | ||||
| 			return err | ||||
| 		} else if err == io.EOF && len(s) == 0 { | ||||
| 			if rows != nil && exp != nil { | ||||
| 				exp.WillReturnRows(rows) | ||||
| 			} | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		if s[0] != '#' { | ||||
| 			r := csv.NewReader(strings.NewReader(s)) | ||||
| 			r.Comma = ',' | ||||
| 			var records [][]string | ||||
| 			records, err = r.ReadAll() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			if rows == nil && len(columns) > 0 { | ||||
| 				rows = m.NewRowsWithColumnDefinition(columns...) | ||||
| 			} else { | ||||
| 				for idx := 0; idx < len(records); idx++ { | ||||
| 					if len(columns) == 0 { | ||||
| 						return fmt.Errorf("csv file not valid, does not have %q line", "# columns ") | ||||
| 					} | ||||
| 					rows = FromCSVString(columns, rows, strings.Join(records[idx], ",")) | ||||
| 				} | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if rows != nil { | ||||
| 			exp.WillReturnRows(rows) | ||||
| 			rows = nil | ||||
| 		} | ||||
|  | ||||
| 		switch { | ||||
| 		case strings.HasPrefix(strings.ToLower(s[2:]), "columns"): | ||||
| 			for _, field := range strings.Split(s[2+len("columns")+1:], ",") { | ||||
| 				args := strings.Split(field, "|") | ||||
|  | ||||
| 				column := sqlmock.NewColumn(args[0]).Nullable(false) | ||||
|  | ||||
| 				if len(args) > 1 { | ||||
| 					for _, arg := range args { | ||||
| 						switch arg { | ||||
| 						case "BOOLEAN", "BOOL": | ||||
| 							column = column.OfType("BOOL", false) | ||||
| 						case "NUMBER", "DECIMAL": | ||||
| 							column = column.OfType("DECIMAL", float64(0.0)).WithPrecisionAndScale(10, 4) | ||||
| 						case "VARCHAR": | ||||
| 							column = column.OfType("VARCHAR", nil) | ||||
| 						case "NULL": | ||||
| 							column = column.Nullable(true) | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				columns = append(columns, column) | ||||
| 			} | ||||
| 		case strings.HasPrefix(strings.ToLower(s[2:]), "begin"): | ||||
| 			m.ExpectBegin() | ||||
| 		case strings.HasPrefix(strings.ToLower(s[2:]), "commit"): | ||||
| 			m.ExpectCommit() | ||||
| 		case strings.HasPrefix(strings.ToLower(s[2:]), "rollback"): | ||||
| 			m.ExpectRollback() | ||||
| 		case strings.HasPrefix(strings.ToLower(s[2:]), "exec "): | ||||
| 			m.ExpectExec(s[2+len("exec "):]) | ||||
| 		case strings.HasPrefix(strings.ToLower(s[2:]), "query "): | ||||
| 			exp = m.ExpectQuery(s[2+len("query "):]) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func Run(ctx context.Context, c client.Client, m sqlmock.Sqlmock, dir string, exts []string) error { | ||||
| 	tcases, err := GetCases(dir, exts) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	g, gctx := errgroup.WithContext(ctx) | ||||
| 	if !strings.Contains(dir, "parallel") { | ||||
| 		g.SetLimit(1) | ||||
| 	} | ||||
|  | ||||
| 	for _, tcase := range tcases { | ||||
|  | ||||
| 		for _, dbfile := range tcase.dbfiles { | ||||
| 			if err = SQLFromFile(m, dbfile); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		tc := tcase | ||||
| 		g.Go(func() error { | ||||
| 			var xrid string | ||||
| 			var gerr error | ||||
|  | ||||
| 			treq, err := NewRequestFromFile(c, tc.reqfile) | ||||
| 			if err != nil { | ||||
| 				gerr = fmt.Errorf("failed to read request from file %s err: %w", tc.reqfile, err) | ||||
| 				return gerr | ||||
| 			} | ||||
|  | ||||
| 			xrid = fmt.Sprintf("%s-%d", treq.Endpoint(), time.Now().Unix()) | ||||
|  | ||||
| 			defer func() { | ||||
| 				if gerr == nil { | ||||
| 					fmt.Printf("test %s xrid: %s status: success\n", filepath.Dir(tc.reqfile), xrid) | ||||
| 				} else { | ||||
| 					fmt.Printf("test %s xrid: %s status: failure error: %v\n", filepath.Dir(tc.reqfile), xrid, err) | ||||
| 				} | ||||
| 			}() | ||||
|  | ||||
| 			data := &codec.Frame{} | ||||
| 			md := metadata.New(1) | ||||
| 			md.Set("X-Request-Id", xrid) | ||||
| 			cerr := c.Call(metadata.NewOutgoingContext(gctx, md), treq, data, client.WithContentType(treq.ContentType())) | ||||
|  | ||||
| 			var rspfile string | ||||
|  | ||||
| 			if tc.errfile != "" { | ||||
| 				rspfile = tc.errfile | ||||
| 			} else if tc.rspfile != "" { | ||||
| 				rspfile = tc.rspfile | ||||
| 			} else { | ||||
| 				gerr = fmt.Errorf("errfile and rspfile is empty") | ||||
| 				return gerr | ||||
| 			} | ||||
|  | ||||
| 			expectRsp, err := NewResponseFromFile(rspfile) | ||||
| 			if err != nil { | ||||
| 				gerr = fmt.Errorf("failed to read response from file %s err: %w", rspfile, err) | ||||
| 				return gerr | ||||
| 			} | ||||
|  | ||||
| 			testCodec, err := getCodec(Codecs, getExt(tc.reqfile)) | ||||
| 			if err != nil { | ||||
| 				gerr = fmt.Errorf("failed to get response file codec err: %w", err) | ||||
| 				return gerr | ||||
| 			} | ||||
|  | ||||
| 			expectCodec, err := getCodec(Codecs, getExt(rspfile)) | ||||
| 			if err != nil { | ||||
| 				gerr = fmt.Errorf("failed to get response file codec err: %w", err) | ||||
| 				return gerr | ||||
| 			} | ||||
|  | ||||
| 			if cerr == nil && tc.errfile != "" { | ||||
| 				gerr = fmt.Errorf("expected err %s not happened", expectRsp.Data) | ||||
| 				return gerr | ||||
| 			} else if cerr != nil && tc.errfile != "" { | ||||
| 				if err = ResponseCompareFunc(expectRsp.Data, cerr, expectCodec, testCodec); err != nil { | ||||
| 					gerr = err | ||||
| 					return gerr | ||||
| 				} | ||||
| 			} else if cerr != nil && tc.errfile == "" { | ||||
| 				gerr = cerr | ||||
| 				return gerr | ||||
| 			} else if cerr == nil && tc.errfile == "" { | ||||
| 				if err = ResponseCompareFunc(expectRsp.Data, data, expectCodec, testCodec); err != nil { | ||||
| 					gerr = err | ||||
| 					return gerr | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			/* | ||||
| 				cf, err := getCodec(c.Options().Codecs, getExt(tc.rspfile)) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			*/ | ||||
|  | ||||
| 			return nil | ||||
| 		}) | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	return g.Wait() | ||||
| } | ||||
|  | ||||
| type Case struct { | ||||
| 	dbfiles []string | ||||
| 	reqfile string | ||||
| 	rspfile string | ||||
| 	errfile string | ||||
| } | ||||
|  | ||||
| func GetCases(dir string, exts []string) ([]Case, error) { | ||||
| 	var tcases []Case | ||||
| 	entries, err := os.ReadDir(dir) | ||||
| 	if len(entries) == 0 && err != nil { | ||||
| 		return tcases, err | ||||
| 	} | ||||
|  | ||||
| 	if exts == nil { | ||||
| 		exts = DefaultExts | ||||
| 	} | ||||
|  | ||||
| 	var dirs []string | ||||
| 	var dbfiles []string | ||||
| 	var reqfile, rspfile, errfile string | ||||
|  | ||||
| 	for _, entry := range entries { | ||||
| 		if entry.IsDir() { | ||||
| 			dirs = append(dirs, filepath.Join(dir, entry.Name())) | ||||
| 			continue | ||||
| 		} | ||||
| 		if info, err := entry.Info(); err != nil { | ||||
| 			return tcases, err | ||||
| 		} else if !info.Mode().IsRegular() { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		for _, ext := range exts { | ||||
| 			if getExt(entry.Name()) == ext { | ||||
| 				name := getNameWithoutExt(entry.Name()) | ||||
| 				switch { | ||||
| 				case strings.HasSuffix(name, "_db"): | ||||
| 					dbfiles = append(dbfiles, filepath.Join(dir, entry.Name())) | ||||
| 				case strings.HasSuffix(name, "_req"): | ||||
| 					reqfile = filepath.Join(dir, entry.Name()) | ||||
| 				case strings.HasSuffix(name, "_rsp"): | ||||
| 					rspfile = filepath.Join(dir, entry.Name()) | ||||
| 				case strings.HasSuffix(name, "_err"): | ||||
| 					errfile = filepath.Join(dir, entry.Name()) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if reqfile != "" && (rspfile != "" || errfile != "") { | ||||
| 		tcases = append(tcases, Case{dbfiles: dbfiles, reqfile: reqfile, rspfile: rspfile, errfile: errfile}) | ||||
| 	} | ||||
|  | ||||
| 	for _, dir = range dirs { | ||||
| 		ntcases, err := GetCases(dir, exts) | ||||
| 		if len(ntcases) == 0 && err != nil { | ||||
| 			return tcases, err | ||||
| 		} else if len(ntcases) == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 		tcases = append(tcases, ntcases...) | ||||
| 	} | ||||
|  | ||||
| 	return tcases, nil | ||||
| } | ||||
							
								
								
									
										72
									
								
								util/test/test_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								util/test/test_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| package test | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/DATA-DOG/go-sqlmock" | ||||
| ) | ||||
|  | ||||
| func Test_SQLFromFile(t *testing.T) { | ||||
| 	ctx := context.TODO() | ||||
| 	db, c, err := sqlmock.New() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer db.Close() | ||||
|  | ||||
| 	if err = SQLFromFile(c, "testdata/result/01_firstcase/Call_db.csv"); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	tx, err := db.BeginTx(ctx, nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	rows, err := tx.QueryContext(ctx, "select * from test;") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	for rows.Next() { | ||||
| 		var id int64 | ||||
| 		var name string | ||||
| 		err = rows.Scan(&id, &name) | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 		if id != 1 || name != "test" { | ||||
| 			t.Fatalf("invalid rows %v %v", id, name) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err = rows.Close(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if err = rows.Err(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if err = tx.Commit(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if err = c.ExpectationsWereMet(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func Test_GetCases(t *testing.T) { | ||||
| 	files, err := GetCases("testdata/", nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if len(files) == 0 { | ||||
| 		t.Fatalf("no files matching") | ||||
| 	} | ||||
|  | ||||
| 	if n := len(files); n != 1 { | ||||
| 		t.Fatalf("invalid number of test cases %d", n) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										6
									
								
								util/test/testdata/result/01_firstcase/Call_db.csv
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								util/test/testdata/result/01_firstcase/Call_db.csv
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| # begin | ||||
| # query select \* from test; | ||||
| # columns id|VARCHAR,name|VARCHAR | ||||
| id,name | ||||
| 1,test | ||||
| # commit | ||||
| 
 | 
							
								
								
									
										1
									
								
								util/test/testdata/result/01_firstcase/Call_req.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								util/test/testdata/result/01_firstcase/Call_req.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| {} | ||||
							
								
								
									
										1
									
								
								util/test/testdata/result/01_firstcase/Call_rsp.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								util/test/testdata/result/01_firstcase/Call_rsp.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| {} | ||||
		Reference in New Issue
	
	Block a user