Skip to content

Commit

Permalink
refactor: make simulate as a utility function (keploy#1761)
Browse files Browse the repository at this point in the history
* refactor: make simulate as a utility

Signed-off-by: Sarthak160 <[email protected]>

* fix: refactors the test util

Signed-off-by: re-Tick <[email protected]>

* refactor: exports the replay service

Signed-off-by: re-Tick <[email protected]>

* refactor: removes commented code

Signed-off-by: re-Tick <[email protected]>

* refactor: change log to debug

Signed-off-by: Sarthak160 <[email protected]>

---------

Signed-off-by: Sarthak160 <[email protected]>
Signed-off-by: re-Tick <[email protected]>
Co-authored-by: re-Tick <[email protected]>
  • Loading branch information
Sarthak160 and re-Tick authored Apr 4, 2024
1 parent c5042ce commit 10063eb
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 48 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ require (
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
github.com/yudai/pp v2.0.1+incompatible // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/term v0.18.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
k8s.io/kube-openapi v0.0.0-20230601164746-7562a1006961 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
Expand Down Expand Up @@ -123,6 +122,7 @@ require (
github.com/xdg-go/stringprep v1.0.4
github.com/yudai/gojsondiff v1.0.0
golang.org/x/sync v0.6.0
golang.org/x/term v0.18.0
sigs.k8s.io/kustomize/kyaml v0.16.0
)

Expand Down
2 changes: 1 addition & 1 deletion pkg/core/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (a *App) ContainerIPv4Addr() string {

func (a *App) SetupDocker() error {
var err error
cont, net, err := parseDockerCmd(a.cmd)
cont, net, err := ParseDockerCmd(a.cmd)
if err != nil {
utils.LogError(a.logger, err, "failed to parse container name from given docker command", zap.String("cmd", a.cmd))
return err
Expand Down
2 changes: 1 addition & 1 deletion pkg/core/app/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func modifyDockerComposeCommand(appCmd, newComposeFile string) string {
return fmt.Sprintf("%s -f %s", appCmd, newComposeFile)
}

func parseDockerCmd(cmd string) (string, string, error) {
func ParseDockerCmd(cmd string) (string, string, error) {
// Regular expression patterns
containerNamePattern := `--name\s+([^\s]+)`
networkNamePattern := `(--network|--net)\s+([^\s]+)`
Expand Down
3 changes: 1 addition & 2 deletions pkg/core/proxy/integrations/http/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"net/url"

"github.com/agnivade/levenshtein"
"github.com/cloudflare/cfssl/log"
"go.keploy.io/server/v2/pkg/core/proxy/integrations"
"go.keploy.io/server/v2/pkg/core/proxy/integrations/util"
"go.keploy.io/server/v2/pkg/models"
Expand Down Expand Up @@ -144,7 +143,7 @@ func findBinaryMatch(mocks []*models.Mock, reqBuff []byte) int {
shingles2 := util.CreateShingles(reqBuff, k)
similarity := util.JaccardSimilarity(shingles1, shingles2)

log.Debugf("Jaccard Similarity:%f\n", similarity)
// log.Debugf("Jaccard Similarity:%f\n", similarity)

if mxSim < similarity {
mxSim = similarity
Expand Down
3 changes: 1 addition & 2 deletions pkg/platform/yaml/mockdb/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,6 @@ func (ys *MockYaml) filterByTimeStamp(_ context.Context, m []*models.Mock, after
for _, mock := range m {
if mock.Version != "api.keploy.io/v1beta1" && mock.Version != "api.keploy.io/v1beta2" {
isNonKeploy = true
continue
}
if mock.Spec.ReqTimestampMock == (time.Time{}) || mock.Spec.ResTimestampMock == (time.Time{}) {
logger.Debug("request or response timestamp of mock is missing")
Expand All @@ -303,7 +302,7 @@ func (ys *MockYaml) filterByTimeStamp(_ context.Context, m []*models.Mock, after
unfilteredMocks = append(unfilteredMocks, mock)
}
if isNonKeploy {
ys.Logger.Warn("Few mocks in the mock File are not recorded by keploy ignoring them")
ys.Logger.Debug("Few mocks in the mock File are not recorded by keploy ignoring them")
}
return filteredMocks, unfilteredMocks
}
85 changes: 44 additions & 41 deletions pkg/service/replay/replay.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@ var totalTests int
var totalTestPassed int
var totalTestFailed int

type replayer struct {
// emulator contains the struct instance that implements RequestEmulator interface. This is done for
// attaching the objects dynamically as plugins.
var emulator RequestEmulator

func SetTestUtilInstance(instance RequestEmulator) {
emulator = instance
}

type Replayer struct {
logger *zap.Logger
testDB TestDB
mockDB MockDB
Expand All @@ -37,7 +45,12 @@ type replayer struct {
}

func NewReplayer(logger *zap.Logger, testDB TestDB, mockDB MockDB, reportDB ReportDB, telemetry Telemetry, instrumentation Instrumentation, config config.Config) Service {
return &replayer{
// set the request emulator for simulating test case requests, if not set
if emulator == nil {
SetTestUtilInstance(NewTestUtils(config.Test.APITimeout, logger))
}

return &Replayer{
logger: logger,
testDB: testDB,
mockDB: mockDB,
Expand All @@ -48,7 +61,7 @@ func NewReplayer(logger *zap.Logger, testDB TestDB, mockDB MockDB, reportDB Repo
}
}

func (r *replayer) Start(ctx context.Context) error {
func (r *Replayer) Start(ctx context.Context) error {

// creating error group to manage proper shutdown of all the go routines and to propagate the error to the caller
g, ctx := errgroup.WithContext(ctx)
Expand Down Expand Up @@ -152,7 +165,7 @@ func (r *replayer) Start(ctx context.Context) error {
return nil
}

func (r *replayer) BootReplay(ctx context.Context) (string, uint64, context.CancelFunc, error) {
func (r *Replayer) BootReplay(ctx context.Context) (string, uint64, context.CancelFunc, error) {

var cancel context.CancelFunc

Expand Down Expand Up @@ -194,11 +207,11 @@ func (r *replayer) BootReplay(ctx context.Context) (string, uint64, context.Canc
return newTestRunID, appID, cancel, nil
}

func (r *replayer) GetAllTestSetIDs(ctx context.Context) ([]string, error) {
func (r *Replayer) GetAllTestSetIDs(ctx context.Context) ([]string, error) {
return r.testDB.GetAllTestSetIDs(ctx)
}

func (r *replayer) RunTestSet(ctx context.Context, testSetID string, testRunID string, appID uint64, serveTest bool) (models.TestSetStatus, error) {
func (r *Replayer) RunTestSet(ctx context.Context, testSetID string, testRunID string, appID uint64, serveTest bool) (models.TestSetStatus, error) {

// creating error group to manage proper shutdown of all the go routines and to propagate the error to the caller
runTestSetErrGrp, runTestSetCtx := errgroup.WithContext(ctx)
Expand Down Expand Up @@ -376,7 +389,26 @@ func (r *replayer) RunTestSet(ctx context.Context, testSetID string, testRunID s
}

started := time.Now().UTC()
resp, loopErr := r.SimulateRequest(runTestSetCtx, appID, testCase, testSetID)

cmdType := utils.FindDockerCmd(r.config.Command)

if cmdType == utils.Docker || cmdType == utils.DockerCompose {

userIP, err := r.instrumentation.GetAppIP(ctx, appID)
if err != nil {
utils.LogError(r.logger, err, "failed to get the app ip")
break
}

testCase.HTTPReq.URL, err = replaceHostToIP(testCase.HTTPReq.URL, userIP)
if err != nil {
utils.LogError(r.logger, err, "failed to replace host to docker container's IP")
break
}
r.logger.Debug("", zap.Any("replaced URL in case of docker env", testCase.HTTPReq.URL))
}

resp, loopErr := emulator.SimulateRequest(runTestSetCtx, appID, testCase, testSetID)
if loopErr != nil {
utils.LogError(r.logger, err, "failed to simulate request")
break
Expand Down Expand Up @@ -531,7 +563,7 @@ func (r *replayer) RunTestSet(ctx context.Context, testSetID string, testRunID s
return testSetStatus, nil
}

func (r *replayer) GetTestSetStatus(ctx context.Context, testRunID string, testSetID string) (models.TestSetStatus, error) {
func (r *Replayer) GetTestSetStatus(ctx context.Context, testRunID string, testSetID string) (models.TestSetStatus, error) {
testReport, err := r.reportDB.GetReport(ctx, testRunID, testSetID)
if err != nil {
return models.TestSetStatusFailed, fmt.Errorf("failed to get report: %w", err)
Expand All @@ -543,36 +575,7 @@ func (r *replayer) GetTestSetStatus(ctx context.Context, testRunID string, testS
return status, nil
}

func (r *replayer) SimulateRequest(ctx context.Context, appID uint64, tc *models.TestCase, testSetID string) (*models.HTTPResp, error) {
switch tc.Kind {
case models.HTTP:
r.logger.Debug("Before simulating the request", zap.Any("Test case", tc))
cmdType := utils.FindDockerCmd(r.config.Command)
if cmdType == utils.Docker || cmdType == utils.DockerCompose {
var err error

userIP, err := r.instrumentation.GetAppIP(ctx, appID)
if err != nil {
utils.LogError(r.logger, err, "failed to get the app ip")
return nil, err
}

tc.HTTPReq.URL, err = replaceHostToIP(tc.HTTPReq.URL, userIP)
if err != nil {
utils.LogError(r.logger, err, "failed to replace host to docker container's IP")
}
r.logger.Debug("", zap.Any("replaced URL in case of docker env", tc.HTTPReq.URL))
}
r.logger.Debug(fmt.Sprintf("the url of the testcase: %v", tc.HTTPReq.URL))
resp, err := pkg.SimulateHTTP(ctx, *tc, testSetID, r.logger, r.config.Test.APITimeout)
r.logger.Debug("After simulating the request", zap.Any("test case id", tc.Name))
r.logger.Debug("After GetResp of the request", zap.Any("test case id", tc.Name))
return resp, err
}
return nil, nil
}

func (r *replayer) compareResp(tc *models.TestCase, actualResponse *models.HTTPResp, testSetID string) (bool, *models.Result) {
func (r *Replayer) compareResp(tc *models.TestCase, actualResponse *models.HTTPResp, testSetID string) (bool, *models.Result) {

noiseConfig := r.config.Test.GlobalNoise.Global
if tsNoise, ok := r.config.Test.GlobalNoise.Testsets[testSetID]; ok {
Expand All @@ -581,7 +584,7 @@ func (r *replayer) compareResp(tc *models.TestCase, actualResponse *models.HTTPR
return match(tc, actualResponse, noiseConfig, r.config.Test.IgnoreOrdering, r.logger)
}

func (r *replayer) printSummary(ctx context.Context, testRunResult bool) {
func (r *Replayer) printSummary(ctx context.Context, testRunResult bool) {
if totalTests > 0 {
testSuiteNames := make([]string, 0, len(completeTestReport))
for testSuiteName := range completeTestReport {
Expand Down Expand Up @@ -644,11 +647,11 @@ func (r *replayer) printSummary(ctx context.Context, testRunResult bool) {
}
}

func (r *replayer) RunApplication(ctx context.Context, appID uint64, opts models.RunOptions) models.AppError {
func (r *Replayer) RunApplication(ctx context.Context, appID uint64, opts models.RunOptions) models.AppError {
return r.instrumentation.Run(ctx, appID, opts)
}

func (r *replayer) ProvideMocks(ctx context.Context) error {
func (r *Replayer) ProvideMocks(ctx context.Context) error {
var stopReason string
var hookCancel context.CancelFunc
defer func() {
Expand Down
6 changes: 6 additions & 0 deletions pkg/service/replay/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,9 @@ type Telemetry interface {
TestRun(success int, failure int, testSets int, runStatus string)
MockTestRun(utilizedMocks int)
}

// RequestEmulator is used to simulate the API requests to the user API. The requests are read from
// the recorded test case of the user app.
type RequestEmulator interface {
SimulateRequest(ctx context.Context, appID uint64, tc *models.TestCase, testSetID string) (*models.HTTPResp, error)
}
29 changes: 29 additions & 0 deletions pkg/service/replay/utils.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package replay

import (
"context"
"fmt"
"net/url"
"strings"

"go.keploy.io/server/v2/config"
"go.keploy.io/server/v2/pkg"
"go.keploy.io/server/v2/pkg/models"
"go.uber.org/zap"
)

type TestReportVerdict struct {
Expand Down Expand Up @@ -44,3 +48,28 @@ func replaceHostToIP(currentURL string, ipAddress string) (string, error) {
// Return the modified URL
return parsedURL.String(), nil
}

type testUtils struct {
logger *zap.Logger
apiTimeout uint64
}

func NewTestUtils(apiTimeout uint64, logger *zap.Logger) RequestEmulator {
return &testUtils{
logger: logger,
apiTimeout: apiTimeout,
}
}

func (t *testUtils) SimulateRequest(ctx context.Context, _ uint64, tc *models.TestCase, testSetID string) (*models.HTTPResp, error) {
switch tc.Kind {
case models.HTTP:
t.logger.Debug("Before simulating the request", zap.Any("Test case", tc))
t.logger.Debug(fmt.Sprintf("the url of the testcase: %v", tc.HTTPReq.URL))
resp, err := pkg.SimulateHTTP(ctx, *tc, testSetID, t.logger, t.apiTimeout)
t.logger.Debug("After simulating the request", zap.Any("test case id", tc.Name))
t.logger.Debug("After GetResp of the request", zap.Any("test case id", tc.Name))
return resp, err
}
return nil, nil
}

0 comments on commit 10063eb

Please sign in to comment.