diff --git a/README.md b/README.md index 54f8fce..6f42f7e 100644 --- a/README.md +++ b/README.md @@ -25,17 +25,17 @@ These mocks/stubs are realistic and frees you up from writing them manually. Kep ```go import( - "github.com/keploy/go-sdk/keploy" - "github.com/keploy/go-sdk/mock" + "github.com/keploy/go-sdk/v2/keploy" ) // Inside your unit test ... -ctx := mock.NewContext(mock.Config{ +err := keploy.New(keploy.Config{ Mode: keploy.MODE_RECORD, // It can be MODE_TEST or MODE_OFF. Default is MODE_TEST. Default MODE_TEST - TestSuite: "" // TestSuite name to record the mock or test the mocks - Path: "", // optional. It can be relative(./internals) or absolute(/users/xyz/...) + Name: "" // TestSuite name to record the mock or test the mocks + Path: "", // optional. It can be relative(./internals) or absolute(/users/xyz/...) EnableKeployLogs: false, // optional. It can be true or false. If it is true keploy logs will be shown in the unit test terminal. Default: false + delay: 10, // by default it is 5 . This delay is for running keploy }) ... ``` @@ -43,17 +43,18 @@ ctx := mock.NewContext(mock.Config{ At the end of the test case you can add the following function which will terminate keploy if not keploy will be running even after unit test is run ```go -mock.KillProcessOnPort() +keploy.KillProcessOnPort() ``` 3. **Mock**: To mock dependency as per the content of the generated file (during testing) - just set the `Mode` config to `keploy.MODE_TEST` eg: ```go -ctx := mock.NewContext(mock.Config{ +err := keploy.New(keploy.Config{ Mode: keploy.MODE_TEST, - TestSuite: "" - Path: "", + Name: "" + Path: "", EnableKeployLogs: false, + delay: 10, }) ``` @@ -75,17 +76,19 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/keploy/go-sdk/keploy" - "github.com/keploy/go-sdk/v2/mock" + "github.com/keploy/go-sdk/v2/keploy" ) -func setup() { - mock.NewContext(mock.Config{ - TestSuite: "test-set-5", - Mode: keploy.MODE_RECORD, +func setup(t *testing.T) { + err := keploy.New(keploy.Config{ + Name: "test-set-5", + Mode: keploy.MODE_TEST, Path: "/home/ubuntu/dont_touch/samples-go/gin-mongo", - EnableKeployLogs: true, + EnableKeployLogs: false, }) + if err != nil { + t.Fatalf("error while running keploy: %v", err) + } dbName, collection := "keploy", "url-shortener" client, err := New("localhost:27017", dbName) if err != nil { @@ -96,7 +99,7 @@ func setup() { } func TestGetURL(t *testing.T) { - setup() + setup(t) // Setting up Gin and routes r := gin.Default() r.GET("/:param", getURL) @@ -114,10 +117,10 @@ func TestGetURL(t *testing.T) { // We're just checking if it can successfully redirect if w.Code != http.StatusSeeOther { - t.Fatalf("Expected HTTP 303 See Other, but got %v", w.Code) + t.Fatalf("Expcd HTTP 303 See Other, but got %v", w.Code) } - mock.KillProcessOnPort() + keploy.KillProcessOnPort() } @@ -132,7 +135,7 @@ func TestPutURL(t *testing.T) { } payload, err := json.Marshal(data) if err != nil { - t.Fatalf("rre: %v\n", err) + t.Fatalf("rrdfe: %v\n", err) } req, err := http.NewRequest(http.MethodPost, "/url", bytes.NewBuffer(payload)) diff --git a/keploy/mock.go b/keploy/mock.go new file mode 100644 index 0000000..928e042 --- /dev/null +++ b/keploy/mock.go @@ -0,0 +1,169 @@ +package keploy + +import ( + "errors" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + "time" + + "go.uber.org/zap" + + "fmt" + "os" +) + +var ( + logger *zap.Logger +) + +type Config struct { + Mode Mode // Keploy mode on which unit test will run. Possible values: MODE_TEST or MODE_RECORD. Default: MODE_TEST + Name string // Name to record the mock or test the mocks + Path string // Path in which Keploy "/mocks" will be generated. Default: current working directroy. + EnableKeployLogs bool + Delay int +} + +func New(conf Config) error { + + var ( + mode = MODE_OFF + err error + path string = conf.Path + keployCmd string + delay int = 5 + ) + + logger, _ = zap.NewDevelopment() + defer func() { + _ = logger.Sync() + }() + + // killing keploy instance if it is running already + KillProcessOnPort() + + if Mode(conf.Mode).Valid() { + mode = Mode(conf.Mode) + } else { + return errors.New("provided keploy mode is invalid, either use MODE_RECORD/MODE_TEST/MODE_OFF") + } + + if conf.Delay > 5 { + delay = conf.Delay + } + + if mode == MODE_OFF { + return nil + } + + // use current directory, if path is not provided or relative in config + if path == "" { + path, err = os.Getwd() + if err != nil { + return fmt.Errorf("no specific path provided and failed to get current working directory %w", err) + } + logger.Info("no specific path provided; defaulting to the current working directory", zap.String("currentDirectoryPath", path)) + } else if path[0] != '/' { + path, err = filepath.Abs(path) + if err != nil { + return fmt.Errorf("failed to get the absolute path from provided path %w", err) + } + } else { + if _, err := os.Stat(path); os.IsNotExist(err) { + return fmt.Errorf("provided path does not exist %w", err) + } + logger.Info("using provided path to store mocks", zap.String("providedPath", path)) + } + + if conf.Name == "" { + return errors.New("provided mock name is empty") + } + + if mode == MODE_RECORD { + if _, err := os.Stat(path + "/stubs/" + conf.Name + "-mocks.yaml"); !os.IsNotExist(err) { + cmd := exec.Command("sudo", "rm", "-rf", path+"/stubs/"+conf.Name+"-mocks.yaml") + _, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to replace existing mock file %w", err) + } + } + if _, err := os.Stat(path + "/stubs/" + conf.Name + "-config.yaml"); !os.IsNotExist(err) { + cmd := exec.Command("sudo", "rm", "-rf", path+"/stubs/"+conf.Name+"-config.yaml") + _, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to replace existing mock file %w", err) + } + } + } + + appPid := os.Getpid() + + recordCmd := "sudo -E /usr/local/bin/keploy mockRecord --pid " + strconv.Itoa(appPid) + " --path " + path + " --mockName " + conf.Name + " --debug" + testCmd := "sudo -E /usr/local/bin/keploy mockTest --pid " + strconv.Itoa(appPid) + " --path " + path + " --mockName " + conf.Name + " --debug" + + if mode == MODE_TEST { + keployCmd = testCmd + } else { + keployCmd = recordCmd + } + + parts := strings.Fields(keployCmd) + cmd := exec.Command(parts[0], parts[1:]...) + if conf.EnableKeployLogs { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } + + if _, err := exec.LookPath("keploy"); err != nil { + return fmt.Errorf("keploy binary not found, please ensure it is installed. Host OS: %s, Architecture: %s. For installing please follow instructions https://github.com/keploy/keploy#quick-installation", runtime.GOOS, runtime.GOARCH) + } + + errChan := make(chan error) + + go func() { + err := cmd.Run() + if err != nil { + errChan <- err + } + close(errChan) + }() + + select { + case err := <-errChan: + if err != nil { + return err + } + return nil + case <-time.After(time.Duration(delay) * time.Second): + return nil + } +} + +func KillProcessOnPort() { + port := 16789 + cmd := exec.Command("sudo", "lsof", "-t", "-i:"+strconv.Itoa(port)) + output, err := cmd.Output() + if _, ok := err.(*exec.ExitError); ok && len(output) == 0 { + return + } else if err != nil { + logger.Error("Failed to execute lsof: %v\n", zap.Error(err)) + return + } + appPid := os.Getpid() + pids := strings.Split(strings.Trim(string(output), "\n"), "\n") + for _, pid := range pids { + if pid != strconv.Itoa(appPid) { + forceKillProcessByPID(pid) + } + } +} + +func forceKillProcessByPID(pid string) { + cmd := exec.Command("sudo", "kill", "-9", pid) + if err := cmd.Run(); err != nil { + logger.Error(fmt.Sprintf("Failed to kill process with PID %s:", pid), zap.Error(err)) + } +} diff --git a/pkg/keploy/mode.go b/keploy/mode.go similarity index 100% rename from pkg/keploy/mode.go rename to keploy/mode.go diff --git a/mock/mock.go b/mock/mock.go deleted file mode 100644 index 1c3176b..0000000 --- a/mock/mock.go +++ /dev/null @@ -1,137 +0,0 @@ -package mock - -import ( - "os/exec" - "path/filepath" - "strconv" - "strings" - "time" - - "github.com/keploy/go-sdk/v2/pkg/keploy" - "go.uber.org/zap" - - "fmt" - "os" -) - -var ( - logger *zap.Logger -) - -type Config struct { - Mode keploy.Mode // Keploy mode on which unit test will run. Possible values: MODE_TEST or MODE_RECORD. Default: MODE_TEST - TestSuite string // TestSuite name to record the mock or test the mocks - Path string // Path in which Keploy "/mocks" will be generated. Default: current working directroy. - EnableKeployLogs bool -} - -func NewContext(conf Config) { - var ( - mode = keploy.MODE_TEST - err error - path string = conf.Path - keployCmd string - ) - - logger, _ = zap.NewDevelopment() - defer func() { - _ = logger.Sync() - }() - - KillProcessOnPort() - - if keploy.Mode(conf.Mode).Valid() { - mode = keploy.Mode(conf.Mode) - } else { - logger.Error("Failed to get mode, running Tests with Keploy MODE_OFF") - mode = keploy.MODE_OFF - } - - // use current directory, if path is not provided or relative in config - if conf.Path == "" { - path, err = os.Getwd() - if err != nil { - logger.Error("Failed to get the path of current directory, running Tests with Keploy OFF_MODE", zap.Error(err)) - mode = keploy.MODE_OFF - } - } else if conf.Path[0] != '/' { - path, err = filepath.Abs(conf.Path) - if err != nil { - logger.Error("Failed to get the absolute path from relative conf.path, running Tests with Keploy MODE_OFF", zap.Error(err)) - mode = keploy.MODE_OFF - } - } - - if conf.TestSuite == "" { - logger.Error("Failed to get test suite name, running Tests with Keploy MODE_OFF") - mode = keploy.MODE_OFF - } - - if mode == keploy.MODE_RECORD { - if _, err := os.Stat(path + "/keploy/" + conf.TestSuite); !os.IsNotExist(err) { - cmd := exec.Command("sudo", "rm", "-rf", path+"/keploy/"+conf.TestSuite) - cmdOutput, err := cmd.CombinedOutput() - if err != nil { - logger.Error("Failed to delete existing directory, running Tests with Keploy MODE_OFF", zap.Error(err), zap.String("cmdOutput", string(cmdOutput))) - return - } - } - } - - if mode == keploy.MODE_OFF { - return - } - - appPid := os.Getpid() - - recordCmd := "sudo -E /usr/local/bin/keploy mockRecord --pid " + strconv.Itoa(appPid) + " --path " + path + " --delay 5" + " --testSuite " + conf.TestSuite - testCmd := "sudo -E /usr/local/bin/keploy mockTest --pid " + strconv.Itoa(appPid) + " --path " + path + " --delay 5" + " --testSuite " + conf.TestSuite - - if mode == keploy.MODE_TEST { - keployCmd = testCmd - } else { - keployCmd = recordCmd - } - - parts := strings.Fields(keployCmd) - cmd := exec.Command(parts[0], parts[1:]...) - if conf.EnableKeployLogs { - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - } - go func() { - err := cmd.Run() - if err != nil { - logger.Error("Failed to run command: %v\n", zap.Error(err)) - return - } - }() - time.Sleep(20 * time.Second) -} - -func KillProcessOnPort() { - port := 16789 - cmd := exec.Command("sudo", "lsof", "-t", "-i:"+strconv.Itoa(port)) - output, err := cmd.Output() - if _, ok := err.(*exec.ExitError); ok && len(output) == 0 { - logger.Debug("No process found for port", zap.Int("port", port)) - return - } else if err != nil { - logger.Error("Failed to execute lsof: %v\n", zap.Error(err)) - return - } - appPid := os.Getpid() - pids := strings.Split(strings.Trim(string(output), "\n"), "\n") - for _, pid := range pids { - if pid != strconv.Itoa(appPid) { - forceKillProcessByPID(pid) - } - } -} - -func forceKillProcessByPID(pid string) { - cmd := exec.Command("sudo", "kill", "-9", pid) - if err := cmd.Run(); err != nil { - logger.Error(fmt.Sprintf("Failed to kill process with PID %s:", pid), zap.Error(err)) - } -}