package actions_test import ( "github.com/go-debos/debos" "github.com/go-debos/debos/actions" "github.com/stretchr/testify/assert" "io/ioutil" "os" "testing" "strings" ) type testRecipe struct { recipe string err string } // Test if incorrect file has been passed func TestParse_incorrect_file(t *testing.T) { var err error var tests = []struct { filename string err string }{ { "non-existing.yaml", "open non-existing.yaml: no such file or directory", }, { "/proc", "read /proc: is a directory", }, } for _, test := range tests { r := actions.Recipe{} err = r.Parse(test.filename, false, false) assert.EqualError(t, err, test.err) } } // Check common recipe syntax func TestParse_syntax(t *testing.T) { var tests = []testRecipe{ // Test if all actions are supported {` architecture: arm64 actions: - action: apt - action: debootstrap - action: download - action: filesystem-deploy - action: image-partition - action: ostree-commit - action: ostree-deploy - action: overlay - action: pack - action: raw - action: run - action: unpack - action: recipe `, "", // Do not expect failure }, // Test of unknown action in list {` architecture: arm64 actions: - action: test_unknown_action `, "Unknown action: test_unknown_action", }, // Test if 'architecture' property absence {` actions: - action: raw `, "Recipe file must have 'architecture' property", }, // Test if no actions listed {` architecture: arm64 `, "Recipe file must have at least one action", }, // Test of wrong syntax in Yaml {`wrong`, "yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `wrong` into actions.Recipe", }, // Test if no actions listed {` architecture: arm64 `, "Recipe file must have at least one action", }, } for _, test := range tests { runTest(t, test) } } // Check template engine func TestParse_template(t *testing.T) { var test = testRecipe{ // Test template variables ` {{ $action:= or .action "download" }} architecture: arm64 actions: - action: {{ $action }} `, "", // Do not expect failure } { // Test of embedded template r := runTest(t, test) assert.Equalf(t, r.Actions[0].String(), "download", "Fail to use embedded variable definition from recipe:%s\n", test.recipe) } { // Test of user-defined template variable var templateVars = map[string]string{ "action": "pack", } r := runTest(t, test, templateVars) assert.Equalf(t, r.Actions[0].String(), "pack", "Fail to redefine variable with user-defined map:%s\n", test.recipe) } } // Test of 'sector' function embedded to recipe package func TestParse_sector(t *testing.T) { var testSector = testRecipe{ // Fail with unknown action ` architecture: arm64 actions: - action: {{ sector 42 }} `, "Unknown action: 21504", } runTest(t, testSector) } func runTest(t *testing.T, test testRecipe, templateVars ...map[string]string) actions.Recipe { file, err := ioutil.TempFile(os.TempDir(), "recipe") assert.Empty(t, err) defer os.Remove(file.Name()) file.WriteString(test.recipe) file.Close() r := actions.Recipe{} if len(templateVars) == 0 { err = r.Parse(file.Name(), false, false) } else { err = r.Parse(file.Name(), false, false, templateVars[0]) } failed := false if len(test.err) > 0 { // Expected error? failed = !assert.EqualError(t, err, test.err) } else { // Unexpected error failed = !assert.Empty(t, err) } if failed { t.Logf("Failed recipe:%s\n", test.recipe) } return r } type subRecipe struct { name string recipe string } type testSubRecipe struct { recipe string subrecipe subRecipe err string } func TestSubRecipe(t *testing.T) { // Embedded recipes var recipeAmd64 = subRecipe { "amd64.yaml", ` architecture: amd64 actions: - action: run command: ok.sh `, } var recipeInheritedArch = subRecipe { "inherited.yaml", ` {{- $architecture := or .architecture "armhf" }} architecture: {{ $architecture }} actions: - action: run command: ok.sh `, } var recipeArmhf = subRecipe { "armhf.yaml", ` architecture: armhf actions: - action: run command: ok.sh `, } var recipeIncluded = subRecipe { "included.yaml", ` architecture: amd64 actions: - action: run command: ok.sh `, } // test recipes var tests = []testSubRecipe { { // Test recipe same architecture OK ` architecture: amd64 actions: - action: recipe recipe: amd64.yaml `, recipeAmd64, "", // Do not expect failure }, { // Test recipe with inherited architecture OK ` architecture: amd64 actions: - action: recipe recipe: inherited.yaml `, recipeInheritedArch, "", // Do not expect failure }, { // Fail with unknown recipe ` architecture: amd64 actions: - action: recipe recipe: unknown_recipe.yaml `, recipeAmd64, "stat /tmp/unknown_recipe.yaml: no such file or directory", }, { // Fail with different architecture recipe ` architecture: amd64 actions: - action: recipe recipe: armhf.yaml `, recipeArmhf, "Expect architecture 'amd64' but got 'armhf'", }, { // Test included_recipe prevents parsing OK ` architecture: amd64 actions: - action: recipe recipe: included.yaml `, recipeIncluded, "", // Do not expect failure }, } for _, test := range tests { runTestWithSubRecipes(t, test) } } func runTestWithSubRecipes(t *testing.T, test testSubRecipe, templateVars ...map[string]string) actions.Recipe { context := debos.DebosContext { &debos.CommonContext{}, "", "" } dir, err := ioutil.TempDir("", "go-debos") assert.Empty(t, err) defer os.RemoveAll(dir) file, err := ioutil.TempFile(dir, "recipe") assert.Empty(t, err) defer os.Remove(file.Name()) file.WriteString(test.recipe) file.Close() file_subrecipe, err := os.Create(dir + "/" + test.subrecipe.name) assert.Empty(t, err) defer os.Remove(file_subrecipe.Name()) file_subrecipe.WriteString(test.subrecipe.recipe) file_subrecipe.Close() r := actions.Recipe{} if len(templateVars) == 0 { err = r.Parse(file.Name(), false, false) } else { err = r.Parse(file.Name(), false, false, templateVars[0]) } // Should not expect error during parse failed := !assert.Empty(t, err) if !failed { context.Architecture = r.Architecture context.RecipeDir = dir for _, a := range r.Actions { if err = a.Verify(&context); err != nil { break } } if len(test.err) > 0 { // Expected error? failed = !assert.EqualError(t, err, strings.Replace(test.err, "/tmp", dir, 1)) } else { // Unexpected error failed = !assert.Empty(t, err) } } if failed { t.Logf("Failed recipe:%s\n", test.recipe) } return r }