|
|
|
@@ -1,6 +1,9 @@
|
|
|
|
|
package jobspec
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
@@ -58,3 +61,223 @@ task "no-cmd" {}
|
|
|
|
|
t.Fatal("expected error for missing command")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestParse_GoldenFiles(t *testing.T) {
|
|
|
|
|
cases := []struct {
|
|
|
|
|
name string
|
|
|
|
|
file string
|
|
|
|
|
wantJob string
|
|
|
|
|
wantJobType string
|
|
|
|
|
wantTasks int
|
|
|
|
|
checkTask func(t *testing.T, s *Spec)
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "single_task",
|
|
|
|
|
file: "valid_single_task.hcl",
|
|
|
|
|
wantJob: "single",
|
|
|
|
|
wantTasks: 1,
|
|
|
|
|
wantJobType: "",
|
|
|
|
|
checkTask: func(t *testing.T, s *Spec) {
|
|
|
|
|
if s.Tasks[0].Name != "solo" {
|
|
|
|
|
t.Errorf("task name = %q, want solo", s.Tasks[0].Name)
|
|
|
|
|
}
|
|
|
|
|
if s.Tasks[0].Command != "/bin/true" {
|
|
|
|
|
t.Errorf("command = %q, want /bin/true", s.Tasks[0].Command)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "multi_task",
|
|
|
|
|
file: "valid_multi_task.hcl",
|
|
|
|
|
wantJob: "multi",
|
|
|
|
|
wantJobType: "batch",
|
|
|
|
|
wantTasks: 3,
|
|
|
|
|
checkTask: func(t *testing.T, s *Spec) {
|
|
|
|
|
byName := map[string]TaskSpec{}
|
|
|
|
|
for _, tk := range s.Tasks {
|
|
|
|
|
byName[tk.Name] = tk
|
|
|
|
|
}
|
|
|
|
|
if _, ok := byName["build"]; !ok {
|
|
|
|
|
t.Errorf("missing task 'build'")
|
|
|
|
|
}
|
|
|
|
|
if _, ok := byName["test"]; !ok {
|
|
|
|
|
t.Errorf("missing task 'test'")
|
|
|
|
|
}
|
|
|
|
|
if len(byName["test"].Env) != 2 {
|
|
|
|
|
t.Errorf("test env count = %d, want 2", len(byName["test"].Env))
|
|
|
|
|
}
|
|
|
|
|
if _, ok := byName["deploy"]; !ok {
|
|
|
|
|
t.Errorf("missing task 'deploy'")
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "env_vars",
|
|
|
|
|
file: "valid_env_vars.hcl",
|
|
|
|
|
wantJob: "envvars",
|
|
|
|
|
wantTasks: 1,
|
|
|
|
|
checkTask: func(t *testing.T, s *Spec) {
|
|
|
|
|
if len(s.Tasks[0].Env) != 3 {
|
|
|
|
|
t.Errorf("env count = %d, want 3", len(s.Tasks[0].Env))
|
|
|
|
|
}
|
|
|
|
|
want := "FOO=bar"
|
|
|
|
|
if s.Tasks[0].Env[0] != want {
|
|
|
|
|
t.Errorf("env[0] = %q, want %q", s.Tasks[0].Env[0], want)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
path := filepath.Join("testdata", tc.file)
|
|
|
|
|
spec, err := ParseFile(path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ParseFile(%s): %v", tc.file, err)
|
|
|
|
|
}
|
|
|
|
|
if spec.Job.Name != tc.wantJob {
|
|
|
|
|
t.Errorf("job name = %q, want %q", spec.Job.Name, tc.wantJob)
|
|
|
|
|
}
|
|
|
|
|
if tc.wantJobType != "" && spec.Job.Type != tc.wantJobType {
|
|
|
|
|
t.Errorf("job type = %q, want %q", spec.Job.Type, tc.wantJobType)
|
|
|
|
|
}
|
|
|
|
|
if len(spec.Tasks) != tc.wantTasks {
|
|
|
|
|
t.Fatalf("tasks = %d, want %d", len(spec.Tasks), tc.wantTasks)
|
|
|
|
|
}
|
|
|
|
|
if tc.checkTask != nil {
|
|
|
|
|
tc.checkTask(t, spec)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestParse_ErrorPaths(t *testing.T) {
|
|
|
|
|
cases := []struct {
|
|
|
|
|
name string
|
|
|
|
|
file string
|
|
|
|
|
wantErr string
|
|
|
|
|
useParse bool
|
|
|
|
|
hcl string
|
|
|
|
|
}{
|
|
|
|
|
{name: "no_tasks", file: "err_no_tasks.hcl", wantErr: "at least one task"},
|
|
|
|
|
{name: "missing_command", file: "err_missing_command.hcl", wantErr: "required"},
|
|
|
|
|
{name: "malformed", file: "err_malformed.hcl", wantErr: "decode hcl"},
|
|
|
|
|
{name: "missing_job", file: "err_missing_job.hcl", wantErr: "Missing job block"},
|
|
|
|
|
}
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
path := filepath.Join("testdata", tc.file)
|
|
|
|
|
_, err := ParseFile(path)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatalf("expected error containing %q, got nil", tc.wantErr)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(err.Error(), tc.wantErr) {
|
|
|
|
|
t.Errorf("error = %q, want it to contain %q", err.Error(), tc.wantErr)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestParse_EmptyFile(t *testing.T) {
|
|
|
|
|
_, err := Parse([]byte(""), "empty.hcl")
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected error for empty file")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestParse_MalformedHCL(t *testing.T) {
|
|
|
|
|
_, err := Parse([]byte("job = "), "bad.hcl")
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected error for malformed HCL")
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(err.Error(), "decode hcl") {
|
|
|
|
|
t.Errorf("error = %q, want it to contain 'decode hcl'", err.Error())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestParseFile_Nonexistent(t *testing.T) {
|
|
|
|
|
_, err := ParseFile(filepath.Join("testdata", "does_not_exist.hcl"))
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected error for nonexistent file")
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(err.Error(), "read spec file") {
|
|
|
|
|
t.Errorf("error = %q, want it to contain 'read spec file'", err.Error())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestParseFile_ReadError(t *testing.T) {
|
|
|
|
|
// Directory exists but is not readable as a file.
|
|
|
|
|
_, err := ParseFile("testdata")
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected error when ParseFile target is a directory")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSpec_Validate(t *testing.T) {
|
|
|
|
|
cases := []struct {
|
|
|
|
|
name string
|
|
|
|
|
spec *Spec
|
|
|
|
|
wantErr string
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "empty_job_name",
|
|
|
|
|
spec: &Spec{Job: JobSpec{Name: " "}, Tasks: []TaskSpec{{Name: "t", Command: "/bin/echo"}}},
|
|
|
|
|
wantErr: "job name is required",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "no_tasks",
|
|
|
|
|
spec: &Spec{Job: JobSpec{Name: "x"}},
|
|
|
|
|
wantErr: "at least one task is required",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "valid",
|
|
|
|
|
spec: &Spec{Job: JobSpec{Name: "x"}, Tasks: []TaskSpec{{Name: "t", Command: "/bin/echo"}}},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
err := tc.spec.Validate()
|
|
|
|
|
if tc.wantErr == "" {
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("Validate: got %v, want nil", err)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatalf("expected error containing %q, got nil", tc.wantErr)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(err.Error(), tc.wantErr) {
|
|
|
|
|
t.Errorf("error = %q, want it to contain %q", err.Error(), tc.wantErr)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSpec_Validate_RoundTripFromParse(t *testing.T) {
|
|
|
|
|
path := filepath.Join("testdata", "valid_single_task.hcl")
|
|
|
|
|
spec, err := ParseFile(path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ParseFile: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if err := spec.Validate(); err != nil {
|
|
|
|
|
t.Errorf("Validate on parsed spec: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestParseFile_GoldenFilesExist(t *testing.T) {
|
|
|
|
|
// Guard against accidentally removing testdata fixtures.
|
|
|
|
|
files := []string{
|
|
|
|
|
"valid_single_task.hcl",
|
|
|
|
|
"valid_multi_task.hcl",
|
|
|
|
|
"valid_env_vars.hcl",
|
|
|
|
|
"err_no_tasks.hcl",
|
|
|
|
|
"err_missing_command.hcl",
|
|
|
|
|
"err_malformed.hcl",
|
|
|
|
|
"err_missing_job.hcl",
|
|
|
|
|
}
|
|
|
|
|
for _, f := range files {
|
|
|
|
|
path := filepath.Join("testdata", f)
|
|
|
|
|
if _, err := os.Stat(path); err != nil {
|
|
|
|
|
t.Errorf("missing testdata fixture %s: %v", f, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|