Skip to content

Commit

Permalink
add tests to verify we can run atmos correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
osterman committed Dec 23, 2024
1 parent b943327 commit d5e90ec
Show file tree
Hide file tree
Showing 2 changed files with 326 additions and 0 deletions.
184 changes: 184 additions & 0 deletions tests/cli_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package tests

import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"regexp"
"testing"

"gopkg.in/yaml.v3"
)

type Expectation struct {
Stdout []string `yaml:"stdout"`
Stderr []string `yaml:"stderr"`
ExitCode int `yaml:"exit_code"`
FileExists []string `yaml:"file_exists"`
FileContains map[string][]string `yaml:"file_contains"`
}

type TestCase struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Enabled bool `yaml:"enabled"`
Workdir string `yaml:"workdir"`
Command string `yaml:"command"`
Args []string `yaml:"args"`
Env map[string]string `yaml:"env"`
Expect Expectation `yaml:"expect"`
}

type TestSuite struct {
Tests []TestCase `yaml:"tests"`
}

func loadTestSuite(filePath string) (*TestSuite, error) {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}

var suite TestSuite
err = yaml.Unmarshal(data, &suite)
if err != nil {
return nil, err
}

return &suite, nil
}

func TestCLICommands(t *testing.T) {
// Capture the starting working directory
startingDir, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get the current working directory: %v", err)
}

testSuite, err := loadTestSuite("test_cases.yaml")
if err != nil {
t.Fatalf("Failed to load test suite: %v", err)
}

for _, tc := range testSuite.Tests {

if !tc.Enabled {
t.Logf("Skipping disabled test: %s", tc.Name)
continue
}

t.Run(tc.Name, func(t *testing.T) {
defer func() {
// Change back to the original working directory after the test
if err := os.Chdir(startingDir); err != nil {
t.Fatalf("Failed to change back to the starting directory: %v", err)
}
}()

// Change to the specified working directory
if tc.Workdir != "" {
err := os.Chdir(tc.Workdir)
if err != nil {
t.Fatalf("Failed to change directory to %q: %v", tc.Workdir, err)
}
}

// Prepare the command
cmd := exec.Command(tc.Command, tc.Args...)

// Set environment variables
envVars := os.Environ()
for key, value := range tc.Env {
envVars = append(envVars, fmt.Sprintf("%s=%s", key, value))
}
cmd.Env = envVars

var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr

// Run the command
err := cmd.Run()

// Validate exit code
exitCode := 0
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
exitCode = exitErr.ExitCode()
}
}
if exitCode != tc.Expect.ExitCode {
t.Errorf("Description: %s", tc.Description)
t.Errorf("Reason: Expected exit code %d, got %d", tc.Expect.ExitCode, exitCode)
}

// Validate stdout
if !verifyOutput(stdout.String(), tc.Expect.Stdout) {
t.Errorf("Description: %s", tc.Description)
t.Errorf("Reason: Stdout did not match expected patterns.")
t.Errorf("Output: %q", stdout.String())
}

// Validate stderr
if !verifyOutput(stderr.String(), tc.Expect.Stderr) {
t.Errorf("Description: %s", tc.Description)
t.Errorf("Reason: Stderr did not match expected patterns.")
t.Errorf("Output: %q", stderr.String())
}

// Validate file existence
if !verifyFileExists(tc.Expect.FileExists) {
t.Errorf("Description: %s", tc.Description)
}

// Validate file contents
if !verifyFileContains(tc.Expect.FileContains) {
t.Errorf("Description: %s", tc.Description)
}
})
}
}

func verifyOutput(output string, patterns []string) bool {
for _, pattern := range patterns {
re, err := regexp.Compile(pattern)
if err != nil {
return false
}
if !re.MatchString(output) {
return false
}
}
return true
}

func verifyFileExists(files []string) bool {
for _, file := range files {
if _, err := os.Stat(file); errors.Is(err, os.ErrNotExist) {
return false
}
}
return true
}

func verifyFileContains(filePatterns map[string][]string) bool {
for file, patterns := range filePatterns {
content, err := ioutil.ReadFile(file)
if err != nil {
return false
}
for _, pattern := range patterns {
re, err := regexp.Compile(pattern)
if err != nil {
return false
}
if !re.Match(content) {
return false
}
}
}
return true
}
142 changes: 142 additions & 0 deletions tests/test_cases.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
tests:
- name: "atmos"
enabled: true
description: "Verify atmos CLI reports missing stacks directory."
workdir: "../"
command: "atmos"
expect:
stdout:
- "atmos.yaml CLI config file specifies the directory for Atmos stacks as stacks,"
- "but the directory does not exist."
exit: 0

- name: atmos --help
enabled: true
description: "Ensure atmos CLI help command lists available commands."
workdir: "../examples/demo-stacks"
command: "atmos"
args:
- "--help"
expect:
stdout:
- "Available Commands:"
- "\\batlantis\\b"
- "\\baws\\b"
- "\\bcompletion\\b"
- "\\bdescribe\\b"
- "\\bdocs\\b"
- "\\bhelmfile\\b"
- "\\bhelp\\b"
- "\\blist\\b"
- "\\bpro\\b"
- "\\bterraform\\b"
- "\\bvalidate\\b"
- "\\bvendor\\b"
- "\\bversion\\b"
- "\\bworkflow\\b"
- "Flags:"
- "for more information about a command"
exit_code: 0

- name: atmos version
enabled: true
description: "Check that atmos version command outputs version details."
workdir: "../examples/demo-stacks"
command: "atmos"
args:
- "version"
expect:
stdout:
- '👽 Atmos \d+\.\d+\.\d+ on [a-z]+/[a-z0-9]+'
stderr: []
exit_code: 0

- name: atmos version --check
enabled: true
description: "Verify atmos version --check command functions correctly."
workdir: "../examples/demo-stacks"
command: "atmos"
args:
- "version"
- "--check"
expect:
stdout:
- '👽 Atmos \d+\.\d+\.\d+ on [a-z]+/[a-z0-9]+'
stderr: []
exit_code: 0

- name: atmos docs
enabled: false
description: "Ensure atmos docs command executes without errors."
workdir: "../"
command: "atmos"
args:
- "docs"
expect:
exit_code: 0

- name: atmos docs myapp
enabled: true
description: "Validate atmos docs command outputs documentation for a specific component."
workdir: "../examples/demo-stacks/"
command: "atmos"
args:
- "docs"
- "myapp"
expect:
stdout:
- "Example Terraform Weather Component"
exit_code: 0

- name: atmos non-existent
enabled: true
description: "Ensure atmos CLI returns an error for a non-existent command."
workdir: "../"
command: "atmos"
args:
- "non-existent"
expect:
stderr:
- 'unknown command "non-existent" for "atmos"'
exit_code: 1

- name: atmos terraform non-existent
enabled: false
description: "Ensure atmos CLI returns an error for a non-existent command."
workdir: "../"
command: "atmos"
args:
- "terraform"
- "non-existent"
expect:
stderr:
- 'unknown command "non-existent" for "atmos"'
exit_code: 1

- name: atmos describe config -f yaml
enabled: true
description: "Ensure atmos CLI outputs the Atmos configuration in YAML."
workdir: "../examples/demo-stacks/"
command: "atmos"
args:
- "describe"
- "config"
- "-f"
- "yaml"
expect:
stdout:
- 'append_user_agent: Atmos/\d+\.\d+\.\d+ \(Cloud Posse; \+https:\/\/atmos\.tools\)'
exit_code: 0

- name: atmos describe config
enabled: true
description: "Ensure atmos CLI outputs the Atmos configuration in JSON."
workdir: "../examples/demo-stacks/"
command: "atmos"
args:
- "describe"
- "config"
expect:
stdout:
- '"append_user_agent": "Atmos/\d+\.\d+\.\d+ \(Cloud Posse; \+https:\/\/atmos\.tools\)"'
exit_code: 0

0 comments on commit d5e90ec

Please sign in to comment.