diff --git a/Makefile b/Makefile index 60e810c66..90cc0ef9c 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ build: go build -o osbuild-pipeline ./cmd/osbuild-pipeline/ go build -o osbuild-upload-azure ./cmd/osbuild-upload-azure/ go build -o osbuild-upload-aws ./cmd/osbuild-upload-aws/ - go build -o osbuild-tests ./cmd/osbuild-tests/ + go test -c -tags=integration -o osbuild-tests ./cmd/osbuild-tests/main_test.go go test -c -tags=integration -o osbuild-weldr-tests ./internal/weldrcheck/ go test -c -tags=integration -o osbuild-dnf-json-tests ./cmd/osbuild-dnf-json-tests/main_test.go go test -c -tags=integration -o osbuild-rcm-tests ./cmd/osbuild-rcm-tests/main_test.go diff --git a/cmd/osbuild-tests/main.go b/cmd/osbuild-tests/main.go deleted file mode 100644 index 2f7ebf32a..000000000 --- a/cmd/osbuild-tests/main.go +++ /dev/null @@ -1,311 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "log" - "os" - "os/exec" - "strings" - "time" - - "github.com/BurntSushi/toml" - "github.com/google/uuid" - "github.com/osbuild/osbuild-composer/internal/blueprint" -) - -func main() { - // Smoke tests, until functionality tested fully - // that the calls succeed and return valid output - runComposerCLI(false, "compose", "types") - runComposerCLI(false, "compose", "status") - runComposerCLI(false, "compose", "list") - runComposerCLI(false, "compose", "list", "waiting") - runComposerCLI(false, "compose", "list", "running") - runComposerCLI(false, "compose", "list", "finished") - runComposerCLI(false, "compose", "list", "failed") - // runCommand(false, "compose", "log", UUID, []) - // runCommand(false, "compose", "cancel", UUID) - // runCommand(false, "compose", "delete", UUID) - // runCommand(false, "compose", "info", UUID) - // runCommand(false, "compose", "metadata", UUID) - // runCommand(false, "compose", "logs", UUID) - // runCommand(false, "compose", "results", UUID) - // runCommand(false, "compose", "image", UUID) - runComposerCLI(false, "blueprints", "list") - // runCommand(false, "blueprints", "show", BLUEPRINT,....) - // runCommand(false, "blueprints", "changes", BLUEPRINT,....) - // runCommand(false, "blueprints", "diff", BLUEPRINT, FROM/NEWEST, TO/NEWEST/WORKSPACE) - // runCommand(false, "blueprints", "save", BLUEPRINT,...) - // runCommand(false, "blueprints", "delete", BLUEPRINT) - // runCommand(false, "blueprints", "depsolve", BLUEPRINT,...) - // runCommand(false, "blueprints", "push", BLUEPRINT.TOML) - // runCommand(false, "blueprints", "freeze", BLUEPRINT,...) - // runCommand(false, "blueprints", "freeze", "show", BLUEPRINT,...) - // runCommand(false, "blueprints", "freeze", "save", BLUEPRINT,...) - // runCommand(false, "blueprints", "tag", BLUEPRINT) - // runCommand(false, "blueprints", "undo", BLUEPRINT, COMMIT) - // runCommand(false, "blueprints", "workspace", BLUEPRINT) - runComposerCLI(false, "modules", "list") - runComposerCLI(false, "projects", "list") - runComposerCLI(false, "projects", "info", "filesystem") - runComposerCLI(false, "projects", "info", "filesystem", "kernel") - runComposerCLI(false, "sources", "list") - // runCommand(false, "sources", "info", "fedora") - // runCommand(false, "sources", "info", "fedora", "fedora-updates") - // runCommand(false, "sources", "add" SOURCES.TOML) - // runCommand(false, "sources", "change" SOURCES.TOML) - // runCommand(false, "sources", "delete" SOURCES.TOML) - runComposerCLI(false, "status", "show") - - // Full integration tests - testCompose("ami") -} - -func testCompose(outputType string) { - tmpdir := NewTemporaryWorkDir("osbuild-tests-") - log.Println(tmpdir.Path) - defer tmpdir.Close() - - bp := blueprint.Blueprint{ - Name: "empty", - Description: "Test empty blueprint in toml format", - } - pushBlueprint(&bp) - defer deleteBlueprint(&bp) - - runComposerCLI(false, "blueprints", "save", "empty") - if _, err := os.Stat("empty.toml"); os.IsNotExist(err) { - log.Fatalf("empty.toml doesn not exist: %v", err) - } - - uuid := startCompose("empty", outputType) - defer deleteCompose(uuid) - status := waitForCompose(uuid) - if status != "FINISHED" { - log.Fatalf("Unexpected compose result: %s", status) - } - - runComposerCLI(false, "compose", "image", uuid.String()) -} - -func startCompose(name, outputType string) uuid.UUID { - var reply struct { - BuildID uuid.UUID `json:"build_id"` - Status bool `json:"status"` - } - rawReply := runComposerCLI(false, "compose", "start", name, outputType) - err := json.Unmarshal(rawReply, &reply) - if err != nil { - log.Fatalf("Unexpected reply: " + err.Error()) - } - if !reply.Status { - log.Fatalf("Unexpected status %v", reply.Status) - } - - return reply.BuildID -} - -func deleteCompose(id uuid.UUID) { - type deleteUUID struct { - ID uuid.UUID `json:"uuid"` - Status bool `json:"status"` - } - var reply struct { - IDs []deleteUUID `json:"uuids"` - Errors []interface{} `json:"errors"` - } - rawReply := runComposerCLI(false, "compose", "delete", id.String()) - err := json.Unmarshal(rawReply, &reply) - if err != nil { - log.Fatalf("Unexpected reply: " + err.Error()) - } - if len(reply.Errors) != 0 { - log.Fatalf("Unexpected errors") - } - if len(reply.IDs) != 1 { - log.Fatalf("Unexpected number of UUIDs returned: %d", len(reply.IDs)) - } - if !reply.IDs[0].Status { - log.Fatalf("Unexpected status %v", reply.IDs[0].Status) - } -} - -func waitForCompose(uuid uuid.UUID) string { - for { - status := getComposeStatus(true, uuid) - if status == "FINISHED" || status == "FAILED" { - return status - } - time.Sleep(time.Second) - } -} - -func getComposeStatus(quiet bool, uuid uuid.UUID) string { - var reply struct { - QueueStatus string `json:"queue_status"` - } - rawReply := runComposerCLI(quiet, "compose", "info", uuid.String()) - err := json.Unmarshal(rawReply, &reply) - if err != nil { - log.Fatalf("Unexpected reply: " + err.Error()) - } - - return reply.QueueStatus -} - -func pushBlueprint(bp *blueprint.Blueprint) { - tmpfile, err := ioutil.TempFile("", "osbuild-test-") - if err != nil { - log.Fatalf("Could not create temporary file: " + err.Error()) - } - defer os.Remove(tmpfile.Name()) - - err = toml.NewEncoder(tmpfile).Encode(bp) - if err != nil { - log.Fatalf("Could not marshapl blueprint TOML: " + err.Error()) - } - if err := tmpfile.Close(); err != nil { - log.Fatalf("Could not close toml file: " + err.Error()) - } - - var reply struct { - Status bool `json:"status"` - } - rawReply := runComposerCLI(false, "blueprints", "push", tmpfile.Name()) - err = json.Unmarshal(rawReply, &reply) - if err != nil { - log.Fatalf("Unexpected reply: " + err.Error()) - } - if !reply.Status { - log.Fatalf("Unexpected status %v", reply.Status) - } -} - -func deleteBlueprint(bp *blueprint.Blueprint) { - var reply struct { - Status bool `json:"status"` - } - rawReply := runComposerCLI(false, "blueprints", "delete", bp.Name) - err := json.Unmarshal(rawReply, &reply) - if err != nil { - log.Fatalf("Unexpected reply: " + err.Error()) - } - if !reply.Status { - log.Fatalf("Unexpected status %v", reply.Status) - } -} - -func runComposerCLI(quiet bool, command ...string) json.RawMessage { - command = append([]string{"--json"}, command...) - cmd := exec.Command("composer-cli", command...) - stdout, err := cmd.StdoutPipe() - if err != nil { - log.Fatalf("Could not create command: " + err.Error()) - } - - if !quiet { - log.Printf("$ composer-cli %s\n", strings.Join(command, " ")) - } - - err = cmd.Start() - if err != nil { - log.Fatalf("Could not start command: %v", err) - } - - var result json.RawMessage - - contents, err := ioutil.ReadAll(stdout) - if err != nil { - log.Fatalf("Could not read stdout from command: " + err.Error()) - } - - if len(contents) != 0 { - err = json.Unmarshal(contents, &result) - if err != nil { - // We did not get JSON, try interpreting it as TOML - var data interface{} - err = toml.Unmarshal(contents, &data) - if err != nil { - log.Fatalf("Could not parse output: %s", err.Error()) - } - buffer := bytes.Buffer{} - err = json.NewEncoder(&buffer).Encode(data) - if err != nil { - log.Fatalf("Could not remarshal TOML to JSON: %s", err.Error()) - } - err = json.NewDecoder(&buffer).Decode(&result) - if err != nil { - log.Fatalf("Could not decode the remarshalled JSON: %s", err.Error()) - } - } - } - - err = cmd.Wait() - if err != nil { - log.Fatalf("Command failed: " + err.Error()) - } - - buffer := bytes.Buffer{} - encoder := json.NewEncoder(&buffer) - encoder.SetIndent("", " ") - err = encoder.Encode(result) - if err != nil { - log.Fatalf("Could not remarshal the recevied JSON: " + err.Error()) - } - - if !quiet { - log.Printf("Return:\n%s\n", buffer.String()) - } - - return result -} - -type TemporaryWorkDir struct { - OldWorkDir string - Path string -} - -// Creates a new temporary directory based on pattern and changes the current -// working directory to it. -// -// Example: -// d := NewTemporaryWorkDir("foo-*") -// defer d.Close() -func NewTemporaryWorkDir(pattern string) TemporaryWorkDir { - var d TemporaryWorkDir - var err error - - d.OldWorkDir, err = os.Getwd() - if err != nil { - log.Fatalf("os.GetWd: %v", err) - } - - d.Path, err = ioutil.TempDir("", pattern) - if err != nil { - log.Fatalf("ioutil.TempDir: %v", err) - } - - err = os.Chdir(d.Path) - if err != nil { - log.Fatalf("os.ChDir: %v", err) - } - - return d -} - -// Change back to the previous working directory and removes the temporary one. -func (d *TemporaryWorkDir) Close() { - var err error - - err = os.Chdir(d.OldWorkDir) - if err != nil { - log.Fatalf("os.ChDir: %v", err) - } - - err = os.RemoveAll(d.Path) - if err != nil { - log.Fatalf("os.RemoveAll: %v", err) - } -} diff --git a/cmd/osbuild-tests/main_test.go b/cmd/osbuild-tests/main_test.go new file mode 100644 index 000000000..056df9b38 --- /dev/null +++ b/cmd/osbuild-tests/main_test.go @@ -0,0 +1,249 @@ +// +build integration + +package main + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "os" + "os/exec" + "time" + + "github.com/BurntSushi/toml" + "github.com/google/uuid" + "github.com/osbuild/osbuild-composer/internal/blueprint" + + "github.com/stretchr/testify/require" + "testing" +) + +func TestComposeIntegration(t *testing.T) { + // Smoke tests, until functionality tested fully + // that the calls succeed and return valid output + runComposerCLI(t, false, "compose", "types") + runComposerCLI(t, false, "compose", "status") + runComposerCLI(t, false, "compose", "list") + runComposerCLI(t, false, "compose", "list", "waiting") + runComposerCLI(t, false, "compose", "list", "running") + runComposerCLI(t, false, "compose", "list", "finished") + runComposerCLI(t, false, "compose", "list", "failed") + // runCommand(false, "compose", "log", UUID, []) + // runCommand(false, "compose", "cancel", UUID) + // runCommand(false, "compose", "delete", UUID) + // runCommand(false, "compose", "info", UUID) + // runCommand(false, "compose", "metadata", UUID) + // runCommand(false, "compose", "logs", UUID) + // runCommand(false, "compose", "results", UUID) + // runCommand(false, "compose", "image", UUID) + runComposerCLI(t, false, "blueprints", "list") + // runCommand(false, "blueprints", "show", BLUEPRINT,....) + // runCommand(false, "blueprints", "changes", BLUEPRINT,....) + // runCommand(false, "blueprints", "diff", BLUEPRINT, FROM/NEWEST, TO/NEWEST/WORKSPACE) + // runCommand(false, "blueprints", "save", BLUEPRINT,...) + // runCommand(false, "blueprints", "delete", BLUEPRINT) + // runCommand(false, "blueprints", "depsolve", BLUEPRINT,...) + // runCommand(false, "blueprints", "push", BLUEPRINT.TOML) + // runCommand(false, "blueprints", "freeze", BLUEPRINT,...) + // runCommand(false, "blueprints", "freeze", "show", BLUEPRINT,...) + // runCommand(false, "blueprints", "freeze", "save", BLUEPRINT,...) + // runCommand(false, "blueprints", "tag", BLUEPRINT) + // runCommand(false, "blueprints", "undo", BLUEPRINT, COMMIT) + // runCommand(false, "blueprints", "workspace", BLUEPRINT) + runComposerCLI(t, false, "modules", "list") + runComposerCLI(t, false, "projects", "list") + runComposerCLI(t, false, "projects", "info", "filesystem") + runComposerCLI(t, false, "projects", "info", "filesystem", "kernel") + runComposerCLI(t, false, "sources", "list") + // runCommand(false, "sources", "info", "fedora") + // runCommand(false, "sources", "info", "fedora", "fedora-updates") + // runCommand(false, "sources", "add" SOURCES.TOML) + // runCommand(false, "sources", "change" SOURCES.TOML) + // runCommand(false, "sources", "delete" SOURCES.TOML) + runComposerCLI(t, false, "status", "show") + + // Full integration tests + testCompose(t, "ami") +} + +func testCompose(t *testing.T, outputType string) { + tmpdir := NewTemporaryWorkDir(t, "osbuild-tests-") + defer tmpdir.Close(t) + + bp := blueprint.Blueprint{ + Name: "empty", + Description: "Test empty blueprint in toml format", + } + pushBlueprint(t, &bp) + defer deleteBlueprint(t, &bp) + + runComposerCLI(t, false, "blueprints", "save", "empty") + _, err := os.Stat("empty.toml") + require.Nilf(t, err, "Error accessing 'empty.toml: %v'", err) + + uuid := startCompose(t, "empty", outputType) + defer deleteCompose(t, uuid) + status := waitForCompose(t, uuid) + require.Equalf(t, status, "FINISHED", "Unexpected compose result: %s", status) + + runComposerCLI(t, false, "compose", "image", uuid.String()) +} + +func startCompose(t *testing.T, name, outputType string) uuid.UUID { + var reply struct { + BuildID uuid.UUID `json:"build_id"` + Status bool `json:"status"` + } + rawReply := runComposerCLI(t, false, "compose", "start", name, outputType) + err := json.Unmarshal(rawReply, &reply) + require.Nilf(t, err, "Unexpected reply: %v", err) + require.Truef(t, reply.Status, "Unexpected status %v", reply.Status) + + return reply.BuildID +} + +func deleteCompose(t *testing.T, id uuid.UUID) { + type deleteUUID struct { + ID uuid.UUID `json:"uuid"` + Status bool `json:"status"` + } + var reply struct { + IDs []deleteUUID `json:"uuids"` + Errors []interface{} `json:"errors"` + } + rawReply := runComposerCLI(t, false, "compose", "delete", id.String()) + err := json.Unmarshal(rawReply, &reply) + require.Nilf(t, err, "Unexpected reply: %v", err) + require.Zerof(t, len(reply.Errors), "Unexpected errors") + require.Equalf(t, len(reply.IDs), 1, "Unexpected number of UUIDs returned: %d", len(reply.IDs)) + require.Truef(t, reply.IDs[0].Status, "Unexpected status %v", reply.IDs[0].Status) +} + +func waitForCompose(t *testing.T, uuid uuid.UUID) string { + for { + status := getComposeStatus(t, true, uuid) + if status == "FINISHED" || status == "FAILED" { + return status + } + time.Sleep(time.Second) + } +} + +func getComposeStatus(t *testing.T, quiet bool, uuid uuid.UUID) string { + var reply struct { + QueueStatus string `json:"queue_status"` + } + rawReply := runComposerCLI(t, quiet, "compose", "info", uuid.String()) + err := json.Unmarshal(rawReply, &reply) + require.Nilf(t, err, "Unexpected reply: %v", err) + + return reply.QueueStatus +} + +func pushBlueprint(t *testing.T, bp *blueprint.Blueprint) { + tmpfile, err := ioutil.TempFile("", "osbuild-test-") + require.Nilf(t, err, "Could not create temporary file: %v", err) + defer os.Remove(tmpfile.Name()) + + err = toml.NewEncoder(tmpfile).Encode(bp) + require.Nilf(t, err, "Could not marshal blueprint TOML: %v", err) + err = tmpfile.Close() + require.Nilf(t, err, "Could not close toml file: %v", err) + + var reply struct { + Status bool `json:"status"` + } + rawReply := runComposerCLI(t, false, "blueprints", "push", tmpfile.Name()) + err = json.Unmarshal(rawReply, &reply) + require.Nilf(t, err, "Unexpected reply: %v", err) + require.Truef(t, reply.Status, "Unexpected status %v", reply.Status) +} + +func deleteBlueprint(t *testing.T, bp *blueprint.Blueprint) { + var reply struct { + Status bool `json:"status"` + } + rawReply := runComposerCLI(t, false, "blueprints", "delete", bp.Name) + err := json.Unmarshal(rawReply, &reply) + require.Nilf(t, err, "Unexpected reply: %v", err) + require.Truef(t, reply.Status, "Unexpected status %v", reply.Status) +} + +func runComposerCLI(t *testing.T, quiet bool, command ...string) json.RawMessage { + command = append([]string{"--json"}, command...) + cmd := exec.Command("composer-cli", command...) + stdout, err := cmd.StdoutPipe() + require.Nilf(t, err, "Could not create command: %v", err) + + err = cmd.Start() + require.Nilf(t, err, "Could not start command: %v", err) + + var result json.RawMessage + + contents, err := ioutil.ReadAll(stdout) + require.Nilf(t, err, "Could not read stdout from command: %v", err) + + if len(contents) != 0 { + err = json.Unmarshal(contents, &result) + if err != nil { + // We did not get JSON, try interpreting it as TOML + var data interface{} + err = toml.Unmarshal(contents, &data) + require.Nilf(t, err, "Could not parse output: %v", err) + buffer := bytes.Buffer{} + err = json.NewEncoder(&buffer).Encode(data) + require.Nilf(t, err, "Could not remarshal TOML to JSON: %v", err) + err = json.NewDecoder(&buffer).Decode(&result) + require.Nilf(t, err, "Could not decode the remarshalled JSON: %v", err) + } + } + + err = cmd.Wait() + require.Nilf(t, err, "Command failed: %v", err) + + buffer := bytes.Buffer{} + encoder := json.NewEncoder(&buffer) + encoder.SetIndent("", " ") + err = encoder.Encode(result) + require.Nilf(t, err, "Could not remarshal the recevied JSON: %v", err) + + return result +} + +type TemporaryWorkDir struct { + OldWorkDir string + Path string +} + +// Creates a new temporary directory based on pattern and changes the current +// working directory to it. +// +// Example: +// d := NewTemporaryWorkDir(t, "foo-*") +// defer d.Close(t) +func NewTemporaryWorkDir(t *testing.T, pattern string) TemporaryWorkDir { + var d TemporaryWorkDir + var err error + + d.OldWorkDir, err = os.Getwd() + require.Nilf(t, err, "os.GetWd: %v", err) + + d.Path, err = ioutil.TempDir("", pattern) + require.Nilf(t, err, "ioutil.TempDir: %v", err) + + err = os.Chdir(d.Path) + require.Nilf(t, err, "os.ChDir: %v", err) + + return d +} + +// Change back to the previous working directory and removes the temporary one. +func (d *TemporaryWorkDir) Close(t *testing.T) { + var err error + + err = os.Chdir(d.OldWorkDir) + require.Nilf(t, err, "os.ChDir: %v", err) + + err = os.RemoveAll(d.Path) + require.Nilf(t, err, "os.RemoveAll: %v", err) +} diff --git a/golang-github-osbuild-composer.spec b/golang-github-osbuild-composer.spec index 30cb5c7b4..9604f46c4 100644 --- a/golang-github-osbuild-composer.spec +++ b/golang-github-osbuild-composer.spec @@ -66,7 +66,6 @@ export GOFLAGS=-mod=vendor %endif %gobuild -o _bin/osbuild-composer %{goipath}/cmd/osbuild-composer %gobuild -o _bin/osbuild-worker %{goipath}/cmd/osbuild-worker -%gobuild -o _bin/osbuild-tests %{goipath}/cmd/osbuild-tests %gobuild -o _bin/osbuild-image-tests %{goipath}/cmd/osbuild-image-tests # Build test binaries with `go test -c`, so that they can take advantage of @@ -75,6 +74,7 @@ export GOFLAGS=-mod=vendor TEST_LDFLAGS="${LDFLAGS:-} -B 0x$(od -N 20 -An -tx1 -w100 /dev/urandom | tr -d ' ')" +go test -c -tags=integration -ldflags="${TEST_LDFLAGS}" -o _bin/osbuild-tests %{goipath}/cmd/osbuild-tests go test -c -tags=integration -ldflags="${TEST_LDFLAGS}" -o _bin/osbuild-dnf-json-tests %{goipath}/cmd/osbuild-dnf-json-tests go test -c -tags=integration -ldflags="${TEST_LDFLAGS}" -o _bin/osbuild-weldr-tests %{goipath}/internal/weldrcheck/ go test -c -tags=integration -ldflags="${TEST_LDFLAGS}" -o _bin/osbuild-rcm-tests %{goipath}/cmd/osbuild-rcm-tests