cucumber_test.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. /*
  2. Copyright 2022 Docker Compose CLI authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package cucumber
  14. import (
  15. "context"
  16. "fmt"
  17. "os"
  18. "path/filepath"
  19. "regexp"
  20. "strings"
  21. "testing"
  22. "github.com/compose-spec/compose-go/loader"
  23. "github.com/cucumber/godog"
  24. "github.com/cucumber/godog/colors"
  25. "github.com/mattn/go-shellwords"
  26. "gotest.tools/v3/icmd"
  27. "github.com/docker/compose/v2/pkg/e2e"
  28. )
  29. func TestCucumber(t *testing.T) {
  30. testingOptions := godog.Options{
  31. TestingT: t,
  32. Paths: []string{"./cucumber-features"},
  33. Output: colors.Colored(os.Stdout),
  34. Format: "pretty",
  35. }
  36. status := godog.TestSuite{
  37. Name: "godogs",
  38. Options: &testingOptions,
  39. ScenarioInitializer: setup,
  40. }.Run()
  41. if status == 2 {
  42. t.SkipNow()
  43. }
  44. if status != 0 {
  45. t.Fatalf("zero status code expected, %d received", status)
  46. }
  47. }
  48. func setup(s *godog.ScenarioContext) {
  49. t := s.TestingT()
  50. projectName := loader.NormalizeProjectName(strings.Split(t.Name(), "/")[1])
  51. cli := e2e.NewCLI(t, e2e.WithEnv(
  52. fmt.Sprintf("COMPOSE_PROJECT_NAME=%s", projectName),
  53. ))
  54. th := testHelper{
  55. T: t,
  56. CLI: cli,
  57. ProjectName: projectName,
  58. }
  59. s.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
  60. cli.RunDockerComposeCmd(t, "down", "--remove-orphans", "-v", "-t", "0")
  61. return ctx, nil
  62. })
  63. s.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) {
  64. cli.RunDockerComposeCmd(t, "down", "--remove-orphans", "-v", "-t", "0")
  65. return ctx, nil
  66. })
  67. s.Step(`^a compose file$`, th.setComposeFile)
  68. s.Step(`^a dockerfile$`, th.setDockerfile)
  69. s.Step(`^I run "compose (.*)"$`, th.runComposeCommand)
  70. s.Step(`^I run "docker (.*)"$`, th.runDockerCommand)
  71. s.Step(`service "(.*)" is "(.*)"$`, th.serviceIsStatus)
  72. s.Step(`output contains "(.*)"$`, th.outputContains(true))
  73. s.Step(`output does not contain "(.*)"$`, th.outputContains(false))
  74. s.Step(`exit code is (\d+)$`, th.exitCodeIs)
  75. }
  76. type testHelper struct {
  77. T *testing.T
  78. ProjectName string
  79. ComposeFile string
  80. TestDir string
  81. CommandOutput string
  82. CommandExitCode int
  83. CLI *e2e.CLI
  84. }
  85. func (th *testHelper) serviceIsStatus(service, status string) error {
  86. serviceContainerName := fmt.Sprintf("%s-%s-1", strings.ToLower(th.ProjectName), service)
  87. statusRegex := fmt.Sprintf("%s.*%s", serviceContainerName, status)
  88. res := th.CLI.RunDockerComposeCmd(th.T, "ps", "-a")
  89. r, _ := regexp.Compile(statusRegex)
  90. if !r.MatchString(res.Combined()) {
  91. return fmt.Errorf("Missing/incorrect ps output:\n%s\nregex:\n%s", res.Combined(), statusRegex)
  92. }
  93. return nil
  94. }
  95. func (th *testHelper) outputContains(expected bool) func(string) error {
  96. return func(substring string) error {
  97. contains := strings.Contains(th.CommandOutput, substring)
  98. if contains && !expected {
  99. return fmt.Errorf("Unexpected substring in output: %s\noutput: %s", substring, th.CommandOutput)
  100. } else if !contains && expected {
  101. return fmt.Errorf("Missing substring in output: %s\noutput: %s", substring, th.CommandOutput)
  102. }
  103. return nil
  104. }
  105. }
  106. func (th *testHelper) exitCodeIs(exitCode int) error {
  107. if exitCode != th.CommandExitCode {
  108. return fmt.Errorf("Wrong exit code: %d expected: %d || command output: %s", th.CommandExitCode, exitCode, th.CommandOutput)
  109. }
  110. return nil
  111. }
  112. func (th *testHelper) runComposeCommand(command string) error {
  113. commandArgs, err := shellwords.Parse(command)
  114. if err != nil {
  115. return err
  116. }
  117. commandArgs = append([]string{"-f", "-"}, commandArgs...)
  118. cmd := th.CLI.NewDockerComposeCmd(th.T, commandArgs...)
  119. cmd.Stdin = strings.NewReader(th.ComposeFile)
  120. cmd.Dir = th.TestDir
  121. res := icmd.RunCmd(cmd)
  122. th.CommandOutput = res.Combined()
  123. th.CommandExitCode = res.ExitCode
  124. return nil
  125. }
  126. func (th *testHelper) runDockerCommand(command string) error {
  127. commandArgs, err := shellwords.Parse(command)
  128. if err != nil {
  129. return err
  130. }
  131. cmd := th.CLI.NewDockerCmd(th.T, commandArgs...)
  132. cmd.Dir = th.TestDir
  133. res := icmd.RunCmd(cmd)
  134. th.CommandOutput = res.Combined()
  135. th.CommandExitCode = res.ExitCode
  136. return nil
  137. }
  138. func (th *testHelper) setComposeFile(composeString string) error {
  139. th.ComposeFile = composeString
  140. return nil
  141. }
  142. func (th *testHelper) setDockerfile(dockerfileString string) error {
  143. tempDir := th.T.TempDir()
  144. th.TestDir = tempDir
  145. err := os.WriteFile(filepath.Join(tempDir, "Dockerfile"), []byte(dockerfileString), 0o644)
  146. if err != nil {
  147. return err
  148. }
  149. return nil
  150. }