From f56625109ef5a53ccbf06282ab0ef2b17eeb9f04 Mon Sep 17 00:00:00 2001 From: Erik Rasmussen Date: Fri, 29 Nov 2024 20:44:40 -0600 Subject: [PATCH] Refactor the gen command to use the new cli generator (#489) * Removed multiple files from pkg/gen/input and pkg/gen/output The commit includes the deletion of several files in the 'pkg/gen/input' and 'pkg/gen/output' directories. This is part of a larger refactoring effort to streamline codebase, improve maintainability, and remove redundant or unnecessary code. * Updated test_all dependencies in Makefile The 'test_all' target in the Makefile has been updated to include new dependencies. These additional binaries are now required for the 'test_all' target to run successfully. * Added test suite for uml2ts package A new test suite has been added for the uml2ts package. This includes importing necessary packages and setting up a context, file system, and error handling. The tests are designed to check conformance of the 'interface' and 'nested_interface' with standard output. A generator description is also provided as part of the testing process. * Refactor test assertion handling The code has been refactored to improve the handling of test assertions. A panic is now thrown if a test contains no assertions, replacing the previous warning log. The error expectation in the generator execution has also been adjusted for better traceability. In addition, a warning is logged when there are no assertions for a specific test in the suite. * Refactor end-to-end tests and improve error message The end-to-end testing code has been refactored for better readability and maintainability. The changes include the use of a new function, `DescribeGenerator`, to simplify test generation. Additionally, the error message displayed when a file cannot be found has been made more descriptive. In the suite test file, the method for reading local TypeScript tests has also been updated to allow specific assertions per test type. * Added stringer interface and logging to file operations - Implemented fmt.Stringer interface in file, fsOutput, reader, and writerOutput structs for better debugging. - Added debug logs for writing to a file, copying filesystems, and copying files in writerOutput. - Counted the number of files copied in writerOutput for more detailed logging. * Added logging and stdout support Enhanced the parsing function with debug logs for better traceability. Added a new boolean field 'stdout' to the 'fromPath' struct in path.go, allowing plugins to expect standard output. Updated static.go to use this new feature by setting 'stdout' as true for the Uml2Ts plugin. * Refactor gen command and error handling Significant changes have been made to the 'gen' command. The code now uses a configuration object instead of parsing arguments directly. Error handling has been improved by adding more checks after each operation, ensuring that any issues are caught and reported immediately. Additionally, the input and output parsing logic has been updated to use new methods from the 'run' package. Finally, the generator execution process has been modified for better clarity and efficiency. * Improved test reliability and refactored Docker image existence check - Enhanced the robustness of tests by adding FlakeAttempts to uml2ts Conformance suite and using ExpectWithOffset in assertions. - Refactored Docker plugin's image existence check into a separate function, ImageExists, for better readability and reusability. - Updated docker plugin tests to use the new ImageExists function. * Optimized string return in writerOutput Removed unnecessary usage of fmt.Sprintf for returning a static string in the writerOutput function. This simplifies the code and potentially improves performance by avoiding unnecessary formatting operations. --- Makefile | 2 +- cmd/ux/e2e_test.go | 10 ++--- cmd/ux/ux_suite_test.go | 5 ++- packages/uml2ts/uml2ts_suite_test.go | 35 ++++++++++++++++ pkg/cmd/gen.go | 46 ++++++++++++++------ pkg/config/run/fs.go | 13 ++++++ pkg/config/run/parse.go | 6 +++ pkg/config/run/reader.go | 5 +++ pkg/config/run/writer.go | 17 +++++++- pkg/conform/assertions.go | 12 +++--- pkg/conform/generator.go | 10 ++--- pkg/contract.go | 2 + pkg/gen/input/fs.go | 30 ------------- pkg/gen/input/parse.go | 22 ---------- pkg/gen/input/reader.go | 25 ----------- pkg/gen/output/fs.go | 48 --------------------- pkg/gen/output/fs_test.go | 63 ---------------------------- pkg/gen/output/output_suite_test.go | 13 ------ pkg/plugin/docker/plugin.go | 33 +++++++++------ pkg/plugin/docker/plugin_test.go | 14 +++++-- pkg/plugin/path.go | 11 +++-- pkg/plugin/static.go | 2 +- pkg/testing/e2e/suite.go | 4 +- 23 files changed, 169 insertions(+), 259 deletions(-) create mode 100644 packages/uml2ts/uml2ts_suite_test.go delete mode 100644 pkg/gen/input/fs.go delete mode 100644 pkg/gen/input/parse.go delete mode 100644 pkg/gen/input/reader.go delete mode 100644 pkg/gen/output/fs.go delete mode 100644 pkg/gen/output/fs_test.go delete mode 100644 pkg/gen/output/output_suite_test.go diff --git a/Makefile b/Makefile index 10b089e5..4b474da7 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,7 @@ clean: bun run --cwd packages/tdl clean bun run --cwd packages/ts clean -test_all: +test_all: bin/ux bin/uml2ts bin/uml2uml $(GINKGO) run -r ./ e2e: .make/go_e2e_test diff --git a/cmd/ux/e2e_test.go b/cmd/ux/e2e_test.go index 0cb1fb88..349f5fef 100644 --- a/cmd/ux/e2e_test.go +++ b/cmd/ux/e2e_test.go @@ -15,15 +15,11 @@ var _ = Describe("End to end", func() { Describe("TypeScript Conformance", FlakeAttempts(5), func() { Describe("stdout", func() { generator := cli.New("ux", - cli.WithArgs("gen", "ts"), + cli.WithArgs("gen", "ts", "-"), cli.ExpectStdout, ) - for test := range typescriptSuite.Tests() { - conform.ItShouldPass(generator, test, - conform.AssertStdout, - ) - } + conform.DescribeGenerator(typescriptSuite, generator) }) }) @@ -51,7 +47,7 @@ var _ = Describe("End to end", func() { out, err := cmd.CombinedOutput() Expect(err).To(HaveOccurred()) - Expect(string(out)).To(Equal("open fkjdslfkdjlsf: no such file or directory\n")) + Expect(string(out)).To(Equal("parsing run config: stat fkjdslfkdjlsf: no such file or directory\n")) }) It("should write to output file", FlakeAttempts(5), func(ctx context.Context) { diff --git a/cmd/ux/ux_suite_test.go b/cmd/ux/ux_suite_test.go index e6cad19a..588cb5b4 100644 --- a/cmd/ux/ux_suite_test.go +++ b/cmd/ux/ux_suite_test.go @@ -34,7 +34,10 @@ func TestUx(t *testing.T) { g.Expect(os.Stat(bin)).NotTo(BeNil()) fs := afero.NewOsFs() - typescriptSuite, err = conform.ReadLocalTypeScriptSuite(ctx, fs) + typescriptSuite, err = conform.ReadLocalGitTests(ctx, fs, "typescript", map[string][]e2e.Assertion{ + "interface": {conform.AssertStdout}, + "nested_interface": {conform.AssertStdout}, + }) g.Expect(err).NotTo(HaveOccurred()) RegisterFailHandler(Fail) diff --git a/packages/uml2ts/uml2ts_suite_test.go b/packages/uml2ts/uml2ts_suite_test.go new file mode 100644 index 00000000..79f777fe --- /dev/null +++ b/packages/uml2ts/uml2ts_suite_test.go @@ -0,0 +1,35 @@ +package uml2ts_test + +import ( + "context" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/spf13/afero" + "github.com/unstoppablemango/tdl/pkg/conform" + "github.com/unstoppablemango/tdl/pkg/gen/cli" + "github.com/unstoppablemango/tdl/pkg/testing/e2e" +) + +var suite e2e.Suite + +func TestUml2ts(t *testing.T) { + g := NewWithT(t) + ctx := context.Background() + fs := afero.NewOsFs() + + var err error + suite, err = conform.ReadLocalGitTests(ctx, fs, "typescript", map[string][]e2e.Assertion{ + "interface": {conform.AssertStdout}, + "nested_interface": {conform.AssertStdout}, + }) + g.Expect(err).NotTo(HaveOccurred()) + + RegisterFailHandler(Fail) + RunSpecs(t, "Uml2ts Suite") +} + +var _ = Describe("uml2ts Conformance", FlakeAttempts(5), func() { + conform.DescribeGenerator(suite, cli.New("uml2ts", cli.ExpectStdout)) +}) diff --git a/pkg/cmd/gen.go b/pkg/cmd/gen.go index a8978c88..f69e823a 100644 --- a/pkg/cmd/gen.go +++ b/pkg/cmd/gen.go @@ -1,13 +1,14 @@ package cmd import ( + "errors" + "github.com/charmbracelet/log" - "github.com/spf13/afero" "github.com/spf13/cobra" + "github.com/unstoppablemango/tdl/internal" "github.com/unstoppablemango/tdl/internal/util" "github.com/unstoppablemango/tdl/pkg/cmd/flags" - "github.com/unstoppablemango/tdl/pkg/gen/input" - "github.com/unstoppablemango/tdl/pkg/gen/output" + "github.com/unstoppablemango/tdl/pkg/config/run" "github.com/unstoppablemango/tdl/pkg/plugin" "github.com/unstoppablemango/tdl/pkg/spec" "github.com/unstoppablemango/tdl/pkg/target" @@ -22,11 +23,16 @@ func NewGen() *cobra.Command { Args: cobra.RangeArgs(1, 3), Run: func(cmd *cobra.Command, args []string) { ctx := cmd.Context() - target, err := target.Parse(args[0]) + config, err := run.ParseArgs(args) + if err != nil { + util.Fail(err) + } + + log.Debug("parsing target") + target, err := target.Parse(config.Target) if err != nil { util.Fail(err) } - log := log.With("target", target) log.Debug("searching for a plugin") plugin, err := plugin.FirstAvailable(target) @@ -35,27 +41,41 @@ func NewGen() *cobra.Command { } log.Debug("searching for a generator") - generator, err := plugin.SinkGenerator(target) + generator, err := plugin.Generator(ctx, target) if err != nil { util.Fail(err) } - fsys := afero.NewOsFs() - input, err := input.ParseArgs(fsys, args[1:]) + log.Debug("parsing inputs") + os := internal.RealOs() + inputs, err := run.ParseInputs(os, config) if err != nil { util.Fail(err) } + if len(inputs) != 1 { + util.Fail(errors.New("only a single input may be provided")) + } - output, err := output.ParseArgs(fsys, args[1:]) + log.Debugf("reading spec: %s", inputs[0]) + spec, err := spec.ReadInput(inputs[0]) if err != nil { util.Fail(err) } - log.Debug("creating pipeline") - pipeline := spec.PipeInput(generator.Execute) + log.Debugf("executing generator: %s", generator) + fs, err := generator.Execute(ctx, spec) + if err != nil { + util.Fail(err) + } + + log.Debug("parsing output") + output, err := run.ParseOutput(os, config) + if err != nil { + util.Fail(err) + } - log.Debug("executing pipeline") - if err := pipeline(ctx, input, output); err != nil { + log.Debugf("writing output: %s %s", fs.Name(), output) + if err = output.Write(fs); err != nil { util.Fail(err) } }, diff --git a/pkg/config/run/fs.go b/pkg/config/run/fs.go index bae5aa9f..928c35bf 100644 --- a/pkg/config/run/fs.go +++ b/pkg/config/run/fs.go @@ -6,6 +6,7 @@ import ( "io/fs" "os" + "github.com/charmbracelet/log" "github.com/spf13/afero" tdl "github.com/unstoppablemango/tdl/pkg" "github.com/unstoppablemango/tdl/pkg/mediatype" @@ -16,6 +17,11 @@ type file struct { media tdl.MediaType } +// String implements tdl.Input. +func (f *file) String() string { + return fmt.Sprintf("file: %s %s", f.Name(), f.media) +} + func (f *file) MediaType() tdl.MediaType { return f.media } @@ -47,6 +53,11 @@ type fsOutput struct { path string } +// String implements tdl.Output. +func (f *fsOutput) String() string { + return fmt.Sprintf("fs: %s %s", f.dest.Name(), f.path) +} + func (f *fsOutput) Write(output afero.Fs) error { stat, err := f.dest.Stat(f.path) if errors.Is(err, os.ErrNotExist) { @@ -68,6 +79,7 @@ func (f *fsOutput) writeFile(output afero.Fs) error { return err } + log.Debugf("writing to file %s", file.Name()) return WriterOutput(file).Write(output) } @@ -76,6 +88,7 @@ func FsOutput(dest afero.Fs, path string) tdl.Output { } func copyFs(src, dest afero.Fs) error { + log.Debugf("copying fs %s to fs %s", src.Name(), dest.Name()) return afero.Walk(src, "", func(path string, info fs.FileInfo, err error) error { if err != nil { diff --git a/pkg/config/run/parse.go b/pkg/config/run/parse.go index e6ed1d94..a1a960fd 100644 --- a/pkg/config/run/parse.go +++ b/pkg/config/run/parse.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/charmbracelet/log" tdl "github.com/unstoppablemango/tdl/pkg" "github.com/unstoppablemango/tdl/pkg/target" uxv1alpha1 "github.com/unstoppablemango/tdl/pkg/unmango/dev/ux/v1alpha1" @@ -23,12 +24,15 @@ func ParseArgs(args []string) (*uxv1alpha1.RunConfig, error) { return nil, errors.New("no input specified") } + log := log.With("args", args) input := &uxv1alpha1.Input{} if len(args) == 1 || args[inputIndex] == "-" { + log.Debug("choosing stdin") input.Value = &uxv1alpha1.Input_Stdin{ Stdin: true, } } else { + log.Debugf("choosing input file: %s", args[inputIndex]) input.Value = &uxv1alpha1.Input_File{ File: &uxv1alpha1.FileInput{ Path: args[inputIndex], @@ -42,10 +46,12 @@ func ParseArgs(args []string) (*uxv1alpha1.RunConfig, error) { } if len(args) > 2 { + log.Debugf("choosing output file: %s", args[outputIndex]) config.Output = &uxv1alpha1.RunConfig_Path{ Path: args[outputIndex], } } else { + log.Debug("choosing stdout") config.Output = &uxv1alpha1.RunConfig_Stdout{ Stdout: true, } diff --git a/pkg/config/run/reader.go b/pkg/config/run/reader.go index 5f75b7de..af04eea8 100644 --- a/pkg/config/run/reader.go +++ b/pkg/config/run/reader.go @@ -15,6 +15,11 @@ type reader struct { media tdl.MediaType } +// String implements tdl.Input. +func (r *reader) String() string { + return fmt.Sprintf("reader: %s", r.media) +} + func (r *reader) MediaType() tdl.MediaType { return r.media } diff --git a/pkg/config/run/writer.go b/pkg/config/run/writer.go index 60bb86f9..9cc4e4d8 100644 --- a/pkg/config/run/writer.go +++ b/pkg/config/run/writer.go @@ -4,6 +4,7 @@ import ( "io" "io/fs" + "github.com/charmbracelet/log" "github.com/spf13/afero" tdl "github.com/unstoppablemango/tdl/pkg" ) @@ -12,8 +13,14 @@ type writerOutput struct { writer io.Writer } +// String implements tdl.Output. +func (w *writerOutput) String() string { + return "writer" +} + func (w *writerOutput) Write(output afero.Fs) error { - return afero.Walk(output, "", + count := 0 + err := afero.Walk(output, "", func(path string, info fs.FileInfo, err error) error { if err != nil { return err @@ -22,15 +29,21 @@ func (w *writerOutput) Write(output afero.Fs) error { return nil } + count++ file, err := output.Open(path) if err != nil { return err } - _, err = io.Copy(w.writer, file) + n, err := io.Copy(w.writer, file) + log.Debugf("wrote %d bytes", n) + return err }, ) + + log.Debugf("copied %d files", count) + return err } func WriterOutput(writer io.Writer) tdl.Output { diff --git a/pkg/conform/assertions.go b/pkg/conform/assertions.go index b277cc25..868860c6 100644 --- a/pkg/conform/assertions.go +++ b/pkg/conform/assertions.go @@ -15,25 +15,25 @@ import ( func AssertStdout(test *e2e.Test, output afero.Fs) { By("opening test output") expected, err := aferox.OpenSingle(test.Expected, "") - Expect(err).NotTo(HaveOccurred()) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) By("reading test output") data, err := io.ReadAll(expected) - Expect(err).NotTo(HaveOccurred()) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) - Expect(output).To(ContainFileWithBytes("stdout", data)) + ExpectWithOffset(1, output).To(ContainFileWithBytes("stdout", data)) } func AssertFile(path string) e2e.Assertion { return func(test *e2e.Test, output afero.Fs) { By("opening test output") expected, err := test.Expected.Open(path) - Expect(err).NotTo(HaveOccurred()) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) By("reading test output") data, err := io.ReadAll(expected) - Expect(err).NotTo(HaveOccurred()) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) - Expect(output).To(ContainFileWithBytes(path, data)) + ExpectWithOffset(1, output).To(ContainFileWithBytes(path, data)) } } diff --git a/pkg/conform/generator.go b/pkg/conform/generator.go index f63beeee..dc5b1b9f 100644 --- a/pkg/conform/generator.go +++ b/pkg/conform/generator.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "github.com/charmbracelet/log" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -13,18 +12,19 @@ import ( ) func ItShouldPass(generator tdl.Generator, test *e2e.Test, assertions ...e2e.Assertion) { + if len(assertions) == 0 { + panic("test contained no assertions") + } + It(fmt.Sprintf("should pass: %s", test.Name), func(ctx context.Context) { By("executing the generator") output, err := generator.Execute(ctx, test.Spec) - Expect(err).NotTo(HaveOccurred()) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) By("performing the given assertions") for _, assert := range assertions { assert(test, output) } - if len(assertions) == 0 { - log.New(GinkgoWriter).Warnf("no assertions for: %s", test.Name) - } }) } diff --git a/pkg/contract.go b/pkg/contract.go index 92d1c220..29705748 100644 --- a/pkg/contract.go +++ b/pkg/contract.go @@ -48,11 +48,13 @@ func (m MediaType) String() string { } type Input interface { + fmt.Stringer io.Reader MediaType() MediaType } type Output interface { + fmt.Stringer Write(afero.Fs) error } diff --git a/pkg/gen/input/fs.go b/pkg/gen/input/fs.go deleted file mode 100644 index c489f1ed..00000000 --- a/pkg/gen/input/fs.go +++ /dev/null @@ -1,30 +0,0 @@ -package input - -import ( - "github.com/spf13/afero" - tdl "github.com/unstoppablemango/tdl/pkg" - "github.com/unstoppablemango/tdl/pkg/mediatype" -) - -type file struct { - afero.File - media tdl.MediaType -} - -func (f *file) MediaType() tdl.MediaType { - return f.media -} - -func Open(fsys afero.Fs, path string) (tdl.Input, error) { - input, err := fsys.Open(path) - if err != nil { - return nil, err - } - - media, err := mediatype.Guess(path) - if err != nil { - return nil, err - } - - return &file{input, media}, nil -} diff --git a/pkg/gen/input/parse.go b/pkg/gen/input/parse.go deleted file mode 100644 index 76dda36b..00000000 --- a/pkg/gen/input/parse.go +++ /dev/null @@ -1,22 +0,0 @@ -package input - -import ( - "fmt" - "os" - - "github.com/spf13/afero" - tdl "github.com/unstoppablemango/tdl/pkg" -) - -func ParseArgs(fsys afero.Fs, args []string) (tdl.Input, error) { - switch len(args) { - case 0: - return Stdin(os.Stdin), nil - case 1: - fallthrough - case 2: - return Open(fsys, args[0]) - default: - return nil, fmt.Errorf("too many arguments: %#v", args) - } -} diff --git a/pkg/gen/input/reader.go b/pkg/gen/input/reader.go deleted file mode 100644 index 9b3f7f32..00000000 --- a/pkg/gen/input/reader.go +++ /dev/null @@ -1,25 +0,0 @@ -package input - -import ( - "io" - - tdl "github.com/unstoppablemango/tdl/pkg" - "github.com/unstoppablemango/tdl/pkg/mediatype" -) - -type reader struct { - io.Reader - media tdl.MediaType -} - -func (r *reader) MediaType() tdl.MediaType { - return r.media -} - -func Reader(r io.Reader, media tdl.MediaType) tdl.Input { - return &reader{r, media} -} - -func Stdin(stdin io.Reader) tdl.Input { - return &reader{stdin, mediatype.ApplicationProtobuf} -} diff --git a/pkg/gen/output/fs.go b/pkg/gen/output/fs.go deleted file mode 100644 index 649bceae..00000000 --- a/pkg/gen/output/fs.go +++ /dev/null @@ -1,48 +0,0 @@ -package output - -import ( - "errors" - "fmt" - "os" - - "github.com/spf13/afero" - tdl "github.com/unstoppablemango/tdl/pkg" - "github.com/unstoppablemango/tdl/pkg/sink" -) - -func Fs(fsys afero.Fs, path string) (tdl.Sink, error) { - stat, err := fsys.Stat(path) - if errors.Is(err, os.ErrNotExist) { - return fsFile(fsys, path) - } - if err != nil { - return nil, err - } - if stat.IsDir() { - return sink.NewFs(fsys, path), nil - } - - return fsFile(fsys, path) -} - -func ParseArgs(fsys afero.Fs, args []string) (tdl.Sink, error) { - switch len(args) { - case 0: - fallthrough - case 1: - return sink.WriteTo(os.Stdout), nil - case 2: - return Fs(fsys, args[1]) - default: - return nil, fmt.Errorf("unsupported args: %#v", args) - } -} - -func fsFile(fsys afero.Fs, path string) (tdl.Sink, error) { - file, err := fsys.Create(path) - if err != nil { - return nil, err - } - - return sink.WriteTo(file), nil -} diff --git a/pkg/gen/output/fs_test.go b/pkg/gen/output/fs_test.go deleted file mode 100644 index 9cc48db4..00000000 --- a/pkg/gen/output/fs_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package output_test - -import ( - "bytes" - "os" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/spf13/afero" - - "github.com/unstoppablemango/tdl/pkg/gen/output" -) - -var _ = Describe("Fs", func() { - var fsys afero.Fs - - BeforeEach(func() { - fsys = afero.NewMemMapFs() - }) - - It("should create a new file", func() { - actual, err := output.Fs(fsys, "thing.txt") - - Expect(err).NotTo(HaveOccurred()) - Expect(actual).NotTo(BeNil()) - _, err = fsys.Stat("thing.txt") - Expect(err).NotTo(HaveOccurred()) - }) - - It("should create a new sink at directory", func() { - err := fsys.Mkdir("dir", os.ModeDir) - Expect(err).NotTo(HaveOccurred()) - - actual, err := output.Fs(fsys, "dir") - - Expect(err).NotTo(HaveOccurred()) - err = actual.WriteUnit("thing.txt", bytes.NewBufferString("fkdjsfk")) - Expect(err).NotTo(HaveOccurred()) - _, err = fsys.Stat("dir/thing.txt") - Expect(err).NotTo(HaveOccurred()) - }) - - Describe("ParseArgs", func() { - It("should work", func() { - err := fsys.Mkdir("dir", os.ModeDir) - Expect(err).NotTo(HaveOccurred()) - - actual, err := output.ParseArgs(fsys, []string{"not important", "dir"}) - - Expect(err).NotTo(HaveOccurred()) - err = actual.WriteUnit("thing.txt", bytes.NewBufferString("fkdjsfk")) - Expect(err).NotTo(HaveOccurred()) - _, err = fsys.Stat("dir/thing.txt") - Expect(err).NotTo(HaveOccurred()) - }) - - It("should not support three args", func() { - _, err := output.ParseArgs(fsys, []string{"fdh", "fdj", "fdkjs"}) - - Expect(err).To(HaveOccurred()) - }) - }) -}) diff --git a/pkg/gen/output/output_suite_test.go b/pkg/gen/output/output_suite_test.go deleted file mode 100644 index 262b1590..00000000 --- a/pkg/gen/output/output_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package output_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestOutput(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Output Suite") -} diff --git a/pkg/plugin/docker/plugin.go b/pkg/plugin/docker/plugin.go index 26df864a..f2c6705b 100644 --- a/pkg/plugin/docker/plugin.go +++ b/pkg/plugin/docker/plugin.go @@ -38,21 +38,13 @@ func (p *plugin) String() string { } func (p *plugin) ensure(ctx context.Context) error { - log.Debug("listing images") - images, err := p.client.ImageList(ctx, image.ListOptions{}) + exists, err := ImageExists(ctx, p.client, p.image) if err != nil { return err } - - log.Debug("searching for existing image", - "images", len(images), - "image", p.image, - ) - for _, i := range images { - if slices.Contains(i.RepoTags, p.image) { - log.Debug("image exists") - return nil - } + if exists { + log.Debug("image exists") + return nil } log.Debug("pulling image") @@ -77,3 +69,20 @@ func (p *plugin) ensure(ctx context.Context) error { func New(client client.APIClient, image string) tdl.Plugin { return &plugin{client, image} } + +func ImageExists(ctx context.Context, c client.APIClient, name string) (bool, error) { + log.Debug("listing images") + images, err := c.ImageList(ctx, image.ListOptions{}) + if err != nil { + return false, err + } + + log.Debug("searching for existing image", "images", len(images), "name", name) + for _, i := range images { + if slices.Contains(i.RepoTags, name) { + return true, nil + } + } + + return false, nil +} diff --git a/pkg/plugin/docker/plugin_test.go b/pkg/plugin/docker/plugin_test.go index 16819911..6045fad6 100644 --- a/pkg/plugin/docker/plugin_test.go +++ b/pkg/plugin/docker/plugin_test.go @@ -40,20 +40,26 @@ var _ = Describe("Plugin", Serial, func() { When("the image does not exist", Label("E2E"), func() { BeforeEach(func(ctx context.Context) { - _, err := testClient.ImageRemove(ctx, - "ghcr.io/unstoppablemango/uml2ts:v0.0.30", + exists, err := docker.ImageExists(ctx, testClient, "ghcr.io/unstoppablemango/uml2ts:v0.0.31") + Expect(err).NotTo(HaveOccurred()) + if !exists { + return + } + + _, err = testClient.ImageRemove(ctx, + "ghcr.io/unstoppablemango/uml2ts:v0.0.31", image.RemoveOptions{}, ) Expect(err).NotTo(HaveOccurred()) }) It("should pull a fresh image", func(ctx context.Context) { - p := docker.New(testClient, "ghcr.io/unstoppablemango/uml2ts:v0.0.30") + p := docker.New(testClient, "ghcr.io/unstoppablemango/uml2ts:v0.0.31") g, err := p.Generator(ctx, nil) Expect(err).NotTo(HaveOccurred()) - Expect(g.String()).To(Equal("ghcr.io/unstoppablemango/uml2ts:v0.0.30")) + Expect(g.String()).To(Equal("ghcr.io/unstoppablemango/uml2ts:v0.0.31")) }) }) }) diff --git a/pkg/plugin/path.go b/pkg/plugin/path.go index 13d7eef7..0e47aac9 100644 --- a/pkg/plugin/path.go +++ b/pkg/plugin/path.go @@ -11,8 +11,9 @@ import ( ) type fromPath struct { - name string - order int + name string + stdout bool + order int } // SinkGenerator implements tdl.Plugin. @@ -32,7 +33,9 @@ func (f fromPath) Generator(context.Context, tdl.Target) (tdl.Generator, error) return nil, fmt.Errorf("from path: %w", err) } - return cli.New(path), nil + return cli.New(path, + cli.WithExpectStdout(f.stdout), + ), nil } // String implements tdl.Plugin. @@ -45,5 +48,5 @@ func (f fromPath) Order() int { } func FromPath(name string) tdl.Plugin { - return &fromPath{name, 69} + return &fromPath{name, false, 69} } diff --git a/pkg/plugin/static.go b/pkg/plugin/static.go index 50b87f96..aebfa92a 100644 --- a/pkg/plugin/static.go +++ b/pkg/plugin/static.go @@ -9,7 +9,7 @@ import ( ) var Uml2Ts tdl.Plugin = NewAggregate( - fromPath{"uml2ts", 50}, + fromPath{"uml2ts", true, 50}, github.NewRelease("tdl-linux-amd64.tar.gz", "0.0.30", github.WithArchiveContents("uml2ts"), ), diff --git a/pkg/testing/e2e/suite.go b/pkg/testing/e2e/suite.go index bfff4656..9f66c7b6 100644 --- a/pkg/testing/e2e/suite.go +++ b/pkg/testing/e2e/suite.go @@ -41,8 +41,8 @@ func (s suite) Tests() iter.Seq2[*Test, []Assertion] { return func(yield func(*Test, []Assertion) bool) { for t := range s.tests { assertions, ok := s.assertions[t.Name] - if !ok { - log.Debugf("no assertions for test: %s", t.Name) + if !ok || len(assertions) == 0 { + log.Warnf("no assertions for test: %s", t.Name) } if !yield(t, assertions) {