From 17dbe5bb138e33fe310b5d9ea7ae9b1193b3db6b Mon Sep 17 00:00:00 2001 From: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Date: Wed, 25 Jan 2023 00:28:05 +0530 Subject: [PATCH] Add E2E for Run template (#1170) * refactor run into functions Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> * renamed Apps struct as App Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> * redirect logs to files. set informational logs. set command dirs Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> * fix linter errors Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> * fix e2e test on non-existent resources-path Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> * more fixes Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> * fix merge conflict Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> * set default ports for zero values Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> * fix linter errors Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> * add more details in error, fix logs in run -f Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> * address review comments Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> * address review comments Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> * add more fixes in template run based on e2e tests Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> * add basic happy path e2e for dapr run -f Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> * add positive and negative e2e tests for run template Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> * address review comments Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --- .gitignore | 2 + Makefile | 9 +- cmd/run.go | 16 +- tests/apps/emit-metrics/app.go | 77 ++++ tests/apps/emit-metrics/go.mod | 3 + tests/apps/processor/app.go | 87 +++++ tests/apps/processor/go.mod | 3 + tests/e2e/spawn/spawn.go | 40 ++ tests/e2e/standalone/commands.go | 16 +- tests/e2e/standalone/init_negative_test.go | 4 +- tests/e2e/standalone/init_test.go | 4 +- tests/e2e/standalone/invoke_test.go | 4 +- tests/e2e/standalone/list_test.go | 4 +- tests/e2e/standalone/publish_test.go | 4 +- tests/e2e/standalone/run_template_test.go | 365 ++++++++++++++++++ tests/e2e/standalone/run_test.go | 4 +- tests/e2e/standalone/stop_test.go | 4 +- tests/e2e/standalone/uninstall_test.go | 4 +- tests/e2e/standalone/utils.go | 4 +- tests/e2e/standalone/version_test.go | 4 +- .../run-template-files/appconfig.yaml | 5 + .../e2e/testdata/run-template-files/dapr.yaml | 12 + .../run-template-files/empty_app_command.yaml | 12 + .../env_var_not_set_dapr.yaml | 10 + .../run-template-files/no_app_command.yaml | 17 + .../wrong_emit_metrics_app_dapr.yaml | 13 + 26 files changed, 694 insertions(+), 33 deletions(-) create mode 100644 tests/apps/emit-metrics/app.go create mode 100644 tests/apps/emit-metrics/go.mod create mode 100644 tests/apps/processor/app.go create mode 100644 tests/apps/processor/go.mod create mode 100644 tests/e2e/standalone/run_template_test.go create mode 100644 tests/e2e/testdata/run-template-files/appconfig.yaml create mode 100644 tests/e2e/testdata/run-template-files/dapr.yaml create mode 100644 tests/e2e/testdata/run-template-files/empty_app_command.yaml create mode 100644 tests/e2e/testdata/run-template-files/env_var_not_set_dapr.yaml create mode 100644 tests/e2e/testdata/run-template-files/no_app_command.yaml create mode 100644 tests/e2e/testdata/run-template-files/wrong_emit_metrics_app_dapr.yaml diff --git a/.gitignore b/.gitignore index 662cf3fbb..19d8ec1aa 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,8 @@ cli # CLI's auto-generated components directory **/components +# Auto generated logs dir inside .dapr directory +**/.dapr/logs test_output.json diff --git a/Makefile b/Makefile index 7b44fcb53..90df0ea35 100644 --- a/Makefile +++ b/Makefile @@ -153,7 +153,7 @@ test: test-deps ################################################################################ .PHONY: test-e2e-k8s test-e2e-k8s: test-deps - gotestsum --jsonfile $(TEST_OUTPUT_FILE) --format standard-verbose -- -timeout 20m -count=1 -tags=e2e ./tests/e2e/kubernetes/... + gotestsum --jsonfile $(TEST_OUTPUT_FILE) --format standard-verbose -- -timeout 20m -count=1 -tags=e2e ./tests/e2e/kubernetes/... ################################################################################ # Build, E2E Tests for Kubernetes # @@ -175,6 +175,13 @@ test-e2e-upgrade: test-deps e2e-build-run-upgrade: build test-e2e-upgrade +################################################################################ +# E2E Tests for Self-Hosted Template exec # +################################################################################ +.PHONY: test-e2e-sh-template +test-e2e-sh-template: test-deps + gotestsum --jsonfile $(TEST_OUTPUT_FILE) --format standard-verbose -- -timeout $(E2E_SH_TEST_TIMEOUT) -count=1 -tags=template ./tests/e2e/standalone/... + ################################################################################ # E2E Tests for Self-Hosted # ################################################################################ diff --git a/cmd/run.go b/cmd/run.go index 6d5fd10b3..b33058633 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -474,7 +474,7 @@ func executeRun(runFilePath string, apps []runfileconfig.App) (bool, error) { runConfig := app.RunConfig err = app.CreateDaprdLogFile() if err != nil { - print.StatusEvent(os.Stderr, print.LogFailure, "Error getting log file for app %q present in %s: %s", runConfig.AppID, runFilePath, err.Error()) + print.StatusEvent(os.Stderr, print.LogFailure, "Error getting daprd log file for app %q present in %s: %s", runConfig.AppID, runFilePath, err.Error()) exitWithError = true break } @@ -490,7 +490,7 @@ func executeRun(runFilePath string, apps []runfileconfig.App) (bool, error) { } else { err = app.CreateAppLogFile() if err != nil { - print.StatusEvent(os.Stderr, print.LogFailure, "Error getting log file for app %q present in %s: %s", runConfig.AppID, runFilePath, err.Error()) + print.StatusEvent(os.Stderr, print.LogFailure, "Error getting app log file for app %q present in %s: %s", runConfig.AppID, runFilePath, err.Error()) exitWithError = true break } @@ -531,7 +531,7 @@ func executeRun(runFilePath string, apps []runfileconfig.App) (bool, error) { } // Stop daprd and app processes for each runState. - _, closeError := gracefullyShutdownAppsAndCloseResources(runStates, apps) + closeError := gracefullyShutdownAppsAndCloseResources(runStates, apps) for _, app := range apps { runConfig := app.RunConfig @@ -551,13 +551,9 @@ func logInformationalStatusToStdout(app runfileconfig.App) { print.InfoStatusEvent(os.Stdout, "Writing log files to directory : %s", app.GetLogsDir()) } -func gracefullyShutdownAppsAndCloseResources(runState []*runExec.RunExec, apps []runfileconfig.App) (bool, error) { - exitWithError := false +func gracefullyShutdownAppsAndCloseResources(runState []*runExec.RunExec, apps []runfileconfig.App) error { for _, s := range runState { - hasErr := stopDaprdAndAppProcesses(s) - if !exitWithError && hasErr { - exitWithError = true - } + stopDaprdAndAppProcesses(s) } var err error // close log file resources. @@ -571,7 +567,7 @@ func gracefullyShutdownAppsAndCloseResources(runState []*runExec.RunExec, apps [ err = hasErr } } - return exitWithError, err + return err } func executeRunWithAppsConfigFile(runFilePath string) { diff --git a/tests/apps/emit-metrics/app.go b/tests/apps/emit-metrics/app.go new file mode 100644 index 000000000..2d69fe5ba --- /dev/null +++ b/tests/apps/emit-metrics/app.go @@ -0,0 +1,77 @@ +/* +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "bytes" + "encoding/json" + "log" + "net/http" + "os" + "time" +) + +type Metrics struct { + MetricsID int `json:"metricsID"` +} + +func main() { + var host string + var port string + client := http.Client{} + if val, ok := os.LookupEnv("DAPR_HTTP_PORT"); !ok { + log.Fatalf("DAPR_HTTP_PORT not automatically injected") + } else { + log.Println("DAPR_HTTP_PORT set to", val) + port = val + } + // DAPR_HOST_ADD needs to be an env set in dapr.yaml file + if val, ok := os.LookupEnv("DAPR_HOST_ADD"); !ok { + log.Fatalf("DAPR_HOST_ADD not set") + } else { + log.Println("DAPR_HOST_ADD set to", val) + host = val + } + finalURL := "http://" + host + ":" + port + "/metrics" + log.Println("Sending metrics to ", finalURL) + for i := 0; i < 2000; i++ { + time.Sleep(1 * time.Second) + metrics := Metrics{ + MetricsID: i, + } + b, err := json.Marshal(metrics) + if err != nil { + log.Println("Got error while marshalling metrics ", err) + continue + } + // Send metrics to Dapr + req, _ := http.NewRequest(http.MethodPost, finalURL, bytes.NewBuffer(b)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("dapr-app-id", "processor") + r, err := client.Do(req) + if err != nil { + log.Println("Got error while sending a request to 'processor' app ", err) + continue + } + defer r.Body.Close() + if r.StatusCode != http.StatusOK { + log.Printf("Error sending metrics with %d to 'processor' app got status code %d\n", i, r.StatusCode) + log.Printf("Status %s \n", r.Status) + continue + } + log.Printf("Metrics with ID %d sent \n", i) + } +} diff --git a/tests/apps/emit-metrics/go.mod b/tests/apps/emit-metrics/go.mod new file mode 100644 index 000000000..2b114ab20 --- /dev/null +++ b/tests/apps/emit-metrics/go.mod @@ -0,0 +1,3 @@ +module emit-metrics + +go 1.19 diff --git a/tests/apps/processor/app.go b/tests/apps/processor/app.go new file mode 100644 index 000000000..7888da375 --- /dev/null +++ b/tests/apps/processor/app.go @@ -0,0 +1,87 @@ +/* +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net" + "net/http" + "os" + "os/signal" + "syscall" + "time" +) + +type handler struct{} + +type Metrics struct { + MetricsID int `json:"metricsID"` +} + +func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + fmt.Println("Received request: ", r.Method) + defer r.Body.Close() + var metrics Metrics + err := json.NewDecoder(r.Body).Decode(&metrics) + if err != nil { + fmt.Println("Error decoding body: ", err) + w.WriteHeader(http.StatusBadRequest) + return + } + fmt.Println("Received metrics: ", metrics) + w.WriteHeader(http.StatusOK) +} + +func main() { + fmt.Println("Starting server in port 9081...") + StartServer(9081, &handler{}) +} + +// StartServer starts a HTTP or HTTP2 server +func StartServer(port int, handler http.Handler) { + // Create a listener + addr := fmt.Sprintf(":%d", port) + ln, err := net.Listen("tcp", addr) + if err != nil { + log.Fatalf("Failed to create listener: %v", err) + } + //nolint:gosec + server := &http.Server{ + Addr: addr, + Handler: handler, + } + + // Stop the server when we get a termination signal + stopCh := make(chan os.Signal, 1) + signal.Notify(stopCh, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGINT) //nolint:staticcheck + go func() { + // Wait for cancelation signal + <-stopCh + log.Println("Shutdown signal received") + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + server.Shutdown(ctx) + }() + + err = server.Serve(ln) + + if err != http.ErrServerClosed { + log.Fatalf("Failed to run server: %v", err) + } + + log.Println("Server shut down") +} diff --git a/tests/apps/processor/go.mod b/tests/apps/processor/go.mod new file mode 100644 index 000000000..116fdbb5b --- /dev/null +++ b/tests/apps/processor/go.mod @@ -0,0 +1,3 @@ +module processor + +go 1.19 diff --git a/tests/e2e/spawn/spawn.go b/tests/e2e/spawn/spawn.go index 52f699830..def48f652 100644 --- a/tests/e2e/spawn/spawn.go +++ b/tests/e2e/spawn/spawn.go @@ -15,8 +15,12 @@ package spawn import ( "bufio" + "bytes" "context" + "fmt" "os/exec" + "syscall" + "time" ) // CommandWithContext runs a command with its arguments in background. @@ -72,3 +76,39 @@ func Command(command string, arguments ...string) (string, error) { return string(outBytes), err } + +// CommandExecWithContext runs a command with its arguments, kills the command after context is done +// and returns the combined stdout, stderr or the error. +func CommandExecWithContext(ctx context.Context, command string, arguments ...string) (string, error) { + cmd := exec.Command(command, arguments...) + var b bytes.Buffer + cmd.Stdout = &b + cmd.Stderr = &b + + err := cmd.Start() + if err != nil { + return "", fmt.Errorf("error starting command : %w", err) + } + + waitErrChan := make(chan error, 1) + go func(errChan chan error) { + waitErr := cmd.Wait() + if waitErr != nil { + fmt.Printf("error waiting for command : %s\n", waitErr) + } + waitErrChan <- waitErr + }(waitErrChan) + <-ctx.Done() + if cmd.ProcessState == nil || !cmd.ProcessState.Exited() { + cmd.Process.Signal(syscall.SIGTERM) + time.Sleep(10 * time.Second) + if cmd.ProcessState == nil || !cmd.ProcessState.Exited() { + err = cmd.Process.Kill() + if err != nil { + return b.String(), fmt.Errorf("error killing command : %w", err) + } + } + } + + return b.String(), <-waitErrChan +} diff --git a/tests/e2e/standalone/commands.go b/tests/e2e/standalone/commands.go index 2c291d6eb..24cfff5ff 100644 --- a/tests/e2e/standalone/commands.go +++ b/tests/e2e/standalone/commands.go @@ -1,5 +1,5 @@ -//go:build e2e -// +build e2e +//go:build e2e || template +// +build e2e template /* Copyright 2022 The Dapr Authors @@ -119,6 +119,18 @@ func cmdRun(unixDomainSocket string, args ...string) (string, error) { return spawn.Command(common.GetDaprPath(), runArgs...) } +// cmdRun runs a Dapr instance and returns the command output and error. +func cmdRunWithContext(ctx context.Context, unixDomainSocket string, args ...string) (string, error) { + runArgs := []string{"run"} + + if unixDomainSocket != "" { + runArgs = append(runArgs, "--unix-domain-socket", unixDomainSocket) + } + + runArgs = append(runArgs, args...) + return spawn.CommandExecWithContext(ctx, common.GetDaprPath(), runArgs...) +} + // cmdStop stops the specified app and returns the command output and error. func cmdStop(appId string, args ...string) (string, error) { stopArgs := append([]string{"stop", "--log-as-json", "--app-id", appId}, args...) diff --git a/tests/e2e/standalone/init_negative_test.go b/tests/e2e/standalone/init_negative_test.go index 562c9a6d1..82ccfabc2 100644 --- a/tests/e2e/standalone/init_negative_test.go +++ b/tests/e2e/standalone/init_negative_test.go @@ -1,5 +1,5 @@ -//go:build e2e -// +build e2e +//go:build e2e && !template +// +build e2e,!template /* Copyright 2022 The Dapr Authors diff --git a/tests/e2e/standalone/init_test.go b/tests/e2e/standalone/init_test.go index d3642152a..7071cfe9d 100644 --- a/tests/e2e/standalone/init_test.go +++ b/tests/e2e/standalone/init_test.go @@ -1,5 +1,5 @@ -//go:build e2e -// +build e2e +//go:build e2e && !template +// +build e2e,!template /* Copyright 2022 The Dapr Authors diff --git a/tests/e2e/standalone/invoke_test.go b/tests/e2e/standalone/invoke_test.go index 9502f52a2..046a7d7ed 100644 --- a/tests/e2e/standalone/invoke_test.go +++ b/tests/e2e/standalone/invoke_test.go @@ -1,5 +1,5 @@ -//go:build e2e -// +build e2e +//go:build e2e && !template +// +build e2e,!template /* Copyright 2022 The Dapr Authors diff --git a/tests/e2e/standalone/list_test.go b/tests/e2e/standalone/list_test.go index 5deba9688..4bb214206 100644 --- a/tests/e2e/standalone/list_test.go +++ b/tests/e2e/standalone/list_test.go @@ -1,5 +1,5 @@ -//go:build e2e -// +build e2e +//go:build e2e && !template +// +build e2e,!template /* Copyright 2022 The Dapr Authors diff --git a/tests/e2e/standalone/publish_test.go b/tests/e2e/standalone/publish_test.go index a44e4daae..7827a8d5a 100644 --- a/tests/e2e/standalone/publish_test.go +++ b/tests/e2e/standalone/publish_test.go @@ -1,5 +1,5 @@ -//go:build e2e -// +build e2e +//go:build e2e && !template +// +build e2e,!template /* Copyright 2022 The Dapr Authors diff --git a/tests/e2e/standalone/run_template_test.go b/tests/e2e/standalone/run_template_test.go new file mode 100644 index 000000000..2a3fd2afa --- /dev/null +++ b/tests/e2e/standalone/run_template_test.go @@ -0,0 +1,365 @@ +//go:build e2e || template +// +build e2e template + +/* +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package standalone_test + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type AppTestOutput struct { + appID string + appLogContents []string + daprdLogContent []string + baseLogDirPath string + appLogDoesNotExist bool +} + +func TestRunWithTemplateFile(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping test on Windows") + } + ensureDaprInstallation(t) + t.Cleanup(func() { + // remove dapr installation after all tests in this function. + must(t, cmdUninstall, "failed to uninstall Dapr") + }) + // These tests are dependent on run template files in ../testdata/run-template-files folder. + + t.Run("invalid template file wrong emit metrics app run", func(t *testing.T) { + t.Cleanup(func() { + // assumption in the test is that there is only one set of app and daprd logs in the logs directory. + os.RemoveAll("../../apps/emit-metrics/.dapr/logs") + os.RemoveAll("../../apps/processor/.dapr/logs") + }) + args := []string{ + "-f", "../testdata/run-template-files/wrong_emit_metrics_app_dapr.yaml", + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + output, err := cmdRunWithContext(ctx, "", args...) + t.Logf(output) + require.NoError(t, err, "run failed") + // Deterministic output for template file, so we can assert line by line + lines := strings.Split(output, "\n") + assert.GreaterOrEqual(t, len(lines), 4, "expected at least 4 lines in output of starting two apps") + assert.Contains(t, lines[1], "Started Dapr with app id \"processor\". HTTP Port: 3510.") + assert.Contains(t, lines[2], "Writing log files to directory") + assert.Contains(t, lines[2], "tests/apps/processor/.dapr/logs") + assert.Contains(t, lines[4], "Started Dapr with app id \"emit-metrics\". HTTP Port: 3511.") + assert.Contains(t, lines[5], "Writing log files to directory") + assert.Contains(t, lines[5], "tests/apps/emit-metrics/.dapr/logs") + assert.Contains(t, output, "Received signal to stop Dapr and app processes. Shutting down Dapr and app processes.") + appTestOutput := AppTestOutput{ + appID: "processor", + baseLogDirPath: "../../apps/processor/.dapr/logs", + daprdLogContent: []string{ + "http server is running on port 3510", + "You're up and running! Dapr logs will appear here.", + }, + } + assertLogOutputForRunTemplateExec(t, appTestOutput) + appTestOutput = AppTestOutput{ + appID: "emit-metrics", + baseLogDirPath: "../../apps/emit-metrics/.dapr/logs", + appLogContents: []string{ + "stat wrongappname.go: no such file or directory", + "The App process exited with error code: exit status 1", + }, + daprdLogContent: []string{ + "termination signal received: shutting down", + "Exited Dapr successfully", + }, + } + assertLogOutputForRunTemplateExec(t, appTestOutput) + }) + + t.Run("valid template file", func(t *testing.T) { + t.Cleanup(func() { + // assumption in the test is that there is only one set of app and daprd logs in the logs directory. + os.RemoveAll("../../apps/emit-metrics/.dapr/logs") + os.RemoveAll("../../apps/processor/.dapr/logs") + }) + args := []string{ + "-f", "../testdata/run-template-files/dapr.yaml", + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + output, err := cmdRunWithContext(ctx, "", args...) + t.Logf(output) + require.NoError(t, err, "run failed") + // Deterministic output for template file, so we can assert line by line + lines := strings.Split(output, "\n") + assert.GreaterOrEqual(t, len(lines), 6, "expected at least 6 lines in output of starting two apps") + assert.Contains(t, lines[0], "Validating config and starting app \"processor\"") + assert.Contains(t, lines[1], "Started Dapr with app id \"processor\". HTTP Port: 3510.") + assert.Contains(t, lines[2], "Writing log files to directory") + assert.Contains(t, lines[2], "tests/apps/processor/.dapr/logs") + assert.Contains(t, lines[3], "Validating config and starting app \"emit-metrics\"") + assert.Contains(t, lines[4], "Started Dapr with app id \"emit-metrics\". HTTP Port: 3511.") + assert.Contains(t, lines[5], "Writing log files to directory") + assert.Contains(t, lines[5], "tests/apps/emit-metrics/.dapr/logs") + assert.Contains(t, output, "Received signal to stop Dapr and app processes. Shutting down Dapr and app processes.") + appTestOutput := AppTestOutput{ + appID: "processor", + baseLogDirPath: "../../apps/processor/.dapr/logs", + appLogContents: []string{ + "Received metrics: {3}", + }, + daprdLogContent: []string{ + "http server is running on port 3510", + "You're up and running! Dapr logs will appear here.", + }, + } + assertLogOutputForRunTemplateExec(t, appTestOutput) + appTestOutput = AppTestOutput{ + appID: "emit-metrics", + baseLogDirPath: "../../apps/emit-metrics/.dapr/logs", + appLogContents: []string{ + "DAPR_HTTP_PORT set to 3511", + "DAPR_HOST_ADD set to localhost", + "Metrics with ID 3 sent", + }, + daprdLogContent: []string{ + "termination signal received: shutting down", + "Exited Dapr successfully", + "Exited App successfully", + }, + } + assertLogOutputForRunTemplateExec(t, appTestOutput) + }) + + t.Run("invalid template file env var not set", func(t *testing.T) { + t.Cleanup(func() { + // assumption in the test is that there is only one set of app and daprd logs in the logs directory. + os.RemoveAll("../../apps/emit-metrics/.dapr/logs") + os.RemoveAll("../../apps/processor/.dapr/logs") + }) + args := []string{ + "-f", "../testdata/run-template-files/env_var_not_set_dapr.yaml", + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + output, err := cmdRunWithContext(ctx, "", args...) + t.Logf(output) + require.NoError(t, err, "run failed") + // Deterministic output for template file, so we can assert line by line + lines := strings.Split(output, "\n") + assert.GreaterOrEqual(t, len(lines), 6, "expected at least 6 lines in output of starting two apps") + assert.Contains(t, lines[1], "Started Dapr with app id \"processor\". HTTP Port: 3510.") + assert.Contains(t, lines[2], "Writing log files to directory") + assert.Contains(t, lines[2], "tests/apps/processor/.dapr/logs") + assert.Contains(t, lines[4], "Started Dapr with app id \"emit-metrics\". HTTP Port: 3511.") + assert.Contains(t, lines[5], "Writing log files to directory") + assert.Contains(t, lines[5], "tests/apps/emit-metrics/.dapr/logs") + assert.Contains(t, output, "Received signal to stop Dapr and app processes. Shutting down Dapr and app processes.") + appTestOutput := AppTestOutput{ + appID: "processor", + baseLogDirPath: "../../apps/processor/.dapr/logs", + daprdLogContent: []string{ + "http server is running on port 3510", + "You're up and running! Dapr logs will appear here.", + }, + } + assertLogOutputForRunTemplateExec(t, appTestOutput) + appTestOutput = AppTestOutput{ + appID: "emit-metrics", + baseLogDirPath: "../../apps/emit-metrics/.dapr/logs", + appLogContents: []string{ + "DAPR_HTTP_PORT set to 3511", + "exit status 1", + "Error exiting App: exit status 1", + }, + daprdLogContent: []string{ + "termination signal received: shutting down", + "Exited Dapr successfully", + }, + } + assertLogOutputForRunTemplateExec(t, appTestOutput) + }) + + t.Run("valid template file no app command", func(t *testing.T) { + t.Cleanup(func() { + // assumption in the test is that there is only one set of app and daprd logs in the logs directory. + os.RemoveAll("../../apps/emit-metrics/.dapr/logs") + os.RemoveAll("../../apps/processor/.dapr/logs") + }) + args := []string{ + "-f", "../testdata/run-template-files/no_app_command.yaml", + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + output, err := cmdRunWithContext(ctx, "", args...) + t.Logf(output) + require.NoError(t, err, "run failed") + // Deterministic output for template file, so we can assert line by line + lines := strings.Split(output, "\n") + assert.GreaterOrEqual(t, len(lines), 7, "expected at least 7 lines in output of starting two apps with one app not having a command") + assert.Contains(t, lines[1], "Started Dapr with app id \"processor\". HTTP Port: 3510.") + assert.Contains(t, lines[2], "Writing log files to directory") + assert.Contains(t, lines[2], "tests/apps/processor/.dapr/logs") + assert.Contains(t, lines[4], "No application command found for app \"emit-metrics\" present in") + assert.Contains(t, lines[5], "Started Dapr with app id \"emit-metrics\". HTTP Port: 3511.") + assert.Contains(t, lines[6], "Writing log files to directory") + assert.Contains(t, lines[6], "tests/apps/emit-metrics/.dapr/logs") + assert.Contains(t, output, "Received signal to stop Dapr and app processes. Shutting down Dapr and app processes.") + appTestOutput := AppTestOutput{ + appID: "processor", + baseLogDirPath: "../../apps/processor/.dapr/logs", + appLogContents: []string{ + "Starting server in port 9081...", + "termination signal received: shutting down", + }, + daprdLogContent: []string{ + "http server is running on port 3510", + "You're up and running! Dapr logs will appear here.", + }, + } + assertLogOutputForRunTemplateExec(t, appTestOutput) + appTestOutput = AppTestOutput{ + appID: "emit-metrics", + baseLogDirPath: "../../apps/emit-metrics/.dapr/logs", + appLogDoesNotExist: true, + daprdLogContent: []string{ + "termination signal received: shutting down", + "Exited Dapr successfully", + }, + } + assertLogOutputForRunTemplateExec(t, appTestOutput) + }) + + t.Run("valid template file empty app command", func(t *testing.T) { + t.Cleanup(func() { + // assumption in the test is that there is only one set of app and daprd logs in the logs directory. + os.RemoveAll("../../apps/emit-metrics/.dapr/logs") + os.RemoveAll("../../apps/processor/.dapr/logs") + }) + args := []string{ + "-f", "../testdata/run-template-files/empty_app_command.yaml", + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + output, err := cmdRunWithContext(ctx, "", args...) + t.Logf(output) + require.Error(t, err, "run must fail") + // Deterministic output for template file, so we can assert line by line + lines := strings.Split(output, "\n") + assert.GreaterOrEqual(t, len(lines), 5, "expected at least 5 lines in output of starting two apps with last app having an empty command") + assert.Contains(t, lines[1], "Started Dapr with app id \"processor\". HTTP Port: 3510.") + assert.Contains(t, lines[2], "Writing log files to directory") + assert.Contains(t, lines[2], "tests/apps/processor/.dapr/logs") + assert.Contains(t, lines[4], "Error starting Dapr and app (\"emit-metrics\"): exec: no command") + appTestOutput := AppTestOutput{ + appID: "processor", + baseLogDirPath: "../../apps/processor/.dapr/logs", + appLogContents: []string{ + "Starting server in port 9081...", + "termination signal received: shutting down", + }, + daprdLogContent: []string{ + "http server is running on port 3510", + "You're up and running! Dapr logs will appear here.", + }, + } + assertLogOutputForRunTemplateExec(t, appTestOutput) + appTestOutput = AppTestOutput{ + appID: "emit-metrics", + baseLogDirPath: "../../apps/emit-metrics/.dapr/logs", + appLogContents: []string{ + "Error starting app process: exec: no command", + }, + daprdLogContent: []string{ + "Error starting Dapr and app (\"emit-metrics\"): exec: no command", + }, + } + assertLogOutputForRunTemplateExec(t, appTestOutput) + }) +} + +func TestRunTemplateFileWithoutDaprInit(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping test on Windows") + } + // remove any dapr installation before this test. + must(t, cmdUninstall, "failed to uninstall Dapr") + t.Run("valid template file without dapr init", func(t *testing.T) { + args := []string{ + "-f", "../testdata/run-template-files/no_app_command.yaml", + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + output, err := cmdRunWithContext(ctx, "", args...) + t.Logf(output) + require.Error(t, err, "run must fail") + assert.Contains(t, output, " Error starting Dapr and app (\"processor\"): fork/exec") + assert.Contains(t, output, "daprd: no such file or directory") + }) +} + +func assertLogOutputForRunTemplateExec(t *testing.T, appTestOutput AppTestOutput) { + // assumption in the test is that there is only one set of app and daprd logs in the logs directory. + // This is true for the tests in this file. + daprdLogFileName, err := lookUpFileFullName(appTestOutput.baseLogDirPath, "daprd") + require.NoError(t, err, "failed to find daprd log file") + daprdLogPath := filepath.Join(appTestOutput.baseLogDirPath, daprdLogFileName) + readAndAssertLogFileContents(t, daprdLogPath, appTestOutput.daprdLogContent) + + if appTestOutput.appLogDoesNotExist { + return + } + appLogFileName, err := lookUpFileFullName(appTestOutput.baseLogDirPath, "app") + require.NoError(t, err, "failed to find app log file") + appLogPath := filepath.Join(appTestOutput.baseLogDirPath, appLogFileName) + readAndAssertLogFileContents(t, appLogPath, appTestOutput.appLogContents) +} + +func readAndAssertLogFileContents(t *testing.T, logFilePath string, expectedContent []string) { + assert.FileExists(t, logFilePath, "log file %s must exist", logFilePath) + fileContents, err := ioutil.ReadFile(logFilePath) + assert.NoError(t, err, "failed to read %s log", logFilePath) + contentString := string(fileContents) + for _, line := range expectedContent { + assert.Contains(t, contentString, line, "expected logline to be present") + } +} + +// lookUpFileFullName looks up the full name of the first file with partial name match in the directory. +func lookUpFileFullName(dirPath, partialFilename string) (string, error) { + // Look for the file in the current directory + files, err := ioutil.ReadDir(dirPath) + if err != nil { + return "", err + } + for _, file := range files { + if strings.Contains(file.Name(), partialFilename) { + return file.Name(), nil + } + } + return "", fmt.Errorf("failed to find file with partial name %s in directory %s", partialFilename, dirPath) +} diff --git a/tests/e2e/standalone/run_test.go b/tests/e2e/standalone/run_test.go index b1f149f73..29b623912 100644 --- a/tests/e2e/standalone/run_test.go +++ b/tests/e2e/standalone/run_test.go @@ -1,5 +1,5 @@ -//go:build e2e -// +build e2e +//go:build e2e && !template +// +build e2e,!template /* Copyright 2022 The Dapr Authors diff --git a/tests/e2e/standalone/stop_test.go b/tests/e2e/standalone/stop_test.go index 20edabfa5..1a7cd9573 100644 --- a/tests/e2e/standalone/stop_test.go +++ b/tests/e2e/standalone/stop_test.go @@ -1,5 +1,5 @@ -//go:build e2e -// +build e2e +//go:build e2e && !template +// +build e2e,!template /* Copyright 2022 The Dapr Authors diff --git a/tests/e2e/standalone/uninstall_test.go b/tests/e2e/standalone/uninstall_test.go index 4ba272b3a..ae1545a99 100644 --- a/tests/e2e/standalone/uninstall_test.go +++ b/tests/e2e/standalone/uninstall_test.go @@ -1,5 +1,5 @@ -//go:build e2e -// +build e2e +//go:build e2e && !template +// +build e2e,!template /* Copyright 2022 The Dapr Authors diff --git a/tests/e2e/standalone/utils.go b/tests/e2e/standalone/utils.go index f613e11a4..e0ec58833 100644 --- a/tests/e2e/standalone/utils.go +++ b/tests/e2e/standalone/utils.go @@ -1,5 +1,5 @@ -//go:build e2e -// +build e2e +//go:build e2e || template +// +build e2e template /* Copyright 2022 The Dapr Authors diff --git a/tests/e2e/standalone/version_test.go b/tests/e2e/standalone/version_test.go index 38ea1e0c1..9996732f6 100644 --- a/tests/e2e/standalone/version_test.go +++ b/tests/e2e/standalone/version_test.go @@ -1,5 +1,5 @@ -//go:build e2e -// +build e2e +//go:build e2e && !template +// +build e2e,!template /* Copyright 2022 The Dapr Authors diff --git a/tests/e2e/testdata/run-template-files/appconfig.yaml b/tests/e2e/testdata/run-template-files/appconfig.yaml new file mode 100644 index 000000000..e474e1288 --- /dev/null +++ b/tests/e2e/testdata/run-template-files/appconfig.yaml @@ -0,0 +1,5 @@ +apiVersion: dapr.io/v1alpha1 +kind: Configuration +metadata: + name: appconfig +spec: diff --git a/tests/e2e/testdata/run-template-files/dapr.yaml b/tests/e2e/testdata/run-template-files/dapr.yaml new file mode 100644 index 000000000..bbc32f094 --- /dev/null +++ b/tests/e2e/testdata/run-template-files/dapr.yaml @@ -0,0 +1,12 @@ +version: 1 +apps: + - app_dir_path: ../../../apps/processor/ + app_port: 9081 + dapr_http_port: 3510 + command: ["go","run", "app.go"] + - app_id: emit-metrics + app_dir_path: ../../../apps/emit-metrics/ + dapr_http_port: 3511 + env: + DAPR_HOST_ADD: localhost + command: ["go","run", "app.go"] diff --git a/tests/e2e/testdata/run-template-files/empty_app_command.yaml b/tests/e2e/testdata/run-template-files/empty_app_command.yaml new file mode 100644 index 000000000..07758d72a --- /dev/null +++ b/tests/e2e/testdata/run-template-files/empty_app_command.yaml @@ -0,0 +1,12 @@ +version: 1 +apps: + - app_dir_path: ../../../apps/processor/ + app_port: 9081 + dapr_http_port: 3510 + command: ["go","run", "app.go"] + - app_id: emit-metrics + app_dir_path: ../../../apps/emit-metrics/ + dapr_http_port: 3511 + env: + DAPR_HOST_ADD: localhost + command: [""] diff --git a/tests/e2e/testdata/run-template-files/env_var_not_set_dapr.yaml b/tests/e2e/testdata/run-template-files/env_var_not_set_dapr.yaml new file mode 100644 index 000000000..b767af697 --- /dev/null +++ b/tests/e2e/testdata/run-template-files/env_var_not_set_dapr.yaml @@ -0,0 +1,10 @@ +version: 1 +apps: + - app_dir_path: ../../../apps/processor/ + app_port: 9081 + dapr_http_port: 3510 + command: ["go","run", "app.go"] + - app_id: emit-metrics + app_dir_path: ../../../apps/emit-metrics/ + dapr_http_port: 3511 # required env var DAPR_HOST_ADD not set in the yaml file + command: ["go","run", "app.go"] diff --git a/tests/e2e/testdata/run-template-files/no_app_command.yaml b/tests/e2e/testdata/run-template-files/no_app_command.yaml new file mode 100644 index 000000000..c24fe9aaa --- /dev/null +++ b/tests/e2e/testdata/run-template-files/no_app_command.yaml @@ -0,0 +1,17 @@ +version: 1 +common: + # Unused resources. This is set so that when dapr is not init validation does not fail. + # Used in test run template when dapr is not init. + resources_path: ../resources + config_file_path: appconfig.yaml +apps: + - app_dir_path: ../../../apps/processor/ + app_port: 9081 + dapr_http_port: 3510 + command: ["go","run", "app.go"] + - app_id: emit-metrics + app_dir_path: ../../../apps/emit-metrics/ + dapr_http_port: 3511 + env: + DAPR_HOST_ADD: localhost + diff --git a/tests/e2e/testdata/run-template-files/wrong_emit_metrics_app_dapr.yaml b/tests/e2e/testdata/run-template-files/wrong_emit_metrics_app_dapr.yaml new file mode 100644 index 000000000..2bc5f33a9 --- /dev/null +++ b/tests/e2e/testdata/run-template-files/wrong_emit_metrics_app_dapr.yaml @@ -0,0 +1,13 @@ +version: 1 +apps: + - app_dir_path: ../../../apps/processor/ + app_port: 9081 + dapr_http_port: 3510 + command: ["go","run", "app.go"] + - app_id: emit-metrics + app_dir_path: ../../../apps/emit-metrics/ + dapr_http_port: 3511 + env: + DAPR_HOST_ADD: localhost + # Set wrong app name + command: ["go","run", "wrongappname.go"]