invoke: backfill tests for plugin execution
authorGabe Rosenhouse <rosenhouse@gmail.com>
Wed, 31 Aug 2016 03:52:47 +0000 (23:52 -0400)
committerGabe Rosenhouse <rosenhouse@gmail.com>
Wed, 31 Aug 2016 03:52:47 +0000 (23:52 -0400)
pkg/invoke/exec.go
pkg/invoke/exec_test.go [new file with mode: 0644]
pkg/invoke/invoke_suite_test.go
plugins/test/noop/debug/debug.go
plugins/test/noop/main.go

index a85eede..d7e38f2 100644 (file)
@@ -18,6 +18,7 @@ import (
        "bytes"
        "encoding/json"
        "fmt"
+       "io"
        "os"
        "os/exec"
 
@@ -57,15 +58,25 @@ func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs) er
 }
 
 func execPlugin(pluginPath string, netconf []byte, args CNIArgs) ([]byte, error) {
+       return defaultRawExec.ExecPlugin(pluginPath, netconf, args.AsEnv())
+}
+
+var defaultRawExec = &RawExec{Stderr: os.Stderr}
+
+type RawExec struct {
+       Stderr io.Writer
+}
+
+func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
        stdout := &bytes.Buffer{}
 
        c := exec.Cmd{
-               Env:    args.AsEnv(),
+               Env:    environ,
                Path:   pluginPath,
                Args:   []string{pluginPath},
-               Stdin:  bytes.NewBuffer(netconf),
+               Stdin:  bytes.NewBuffer(stdinData),
                Stdout: stdout,
-               Stderr: os.Stderr,
+               Stderr: e.Stderr,
        }
        if err := c.Run(); err != nil {
                return nil, pluginErr(err, stdout.Bytes())
diff --git a/pkg/invoke/exec_test.go b/pkg/invoke/exec_test.go
new file mode 100644 (file)
index 0000000..7df60a1
--- /dev/null
@@ -0,0 +1,123 @@
+// Copyright 2016 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package invoke_test
+
+import (
+       "bytes"
+       "io/ioutil"
+       "os"
+
+       "github.com/containernetworking/cni/pkg/invoke"
+
+       noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
+
+       . "github.com/onsi/ginkgo"
+       . "github.com/onsi/gomega"
+)
+
+var _ = Describe("RawExec", func() {
+       var (
+               debugFileName string
+               debug         *noop_debug.Debug
+               environ       []string
+               stdin         []byte
+               execer        *invoke.RawExec
+       )
+
+       const reportResult = `{ "some": "result" }`
+
+       BeforeEach(func() {
+               debugFile, err := ioutil.TempFile("", "cni_debug")
+               Expect(err).NotTo(HaveOccurred())
+               Expect(debugFile.Close()).To(Succeed())
+               debugFileName = debugFile.Name()
+
+               debug = &noop_debug.Debug{
+                       ReportResult: reportResult,
+                       ReportStderr: "some stderr message",
+               }
+               Expect(debug.WriteDebug(debugFileName)).To(Succeed())
+
+               environ = []string{
+                       "CNI_COMMAND=ADD",
+                       "CNI_CONTAINERID=some-container-id",
+                       "CNI_ARGS=DEBUG=" + debugFileName,
+                       "CNI_NETNS=/some/netns/path",
+                       "CNI_PATH=/some/bin/path",
+                       "CNI_IFNAME=some-eth0",
+               }
+               stdin = []byte(`{"some":"stdin-json"}`)
+               execer = &invoke.RawExec{}
+       })
+
+       AfterEach(func() {
+               Expect(os.Remove(debugFileName)).To(Succeed())
+       })
+
+       It("runs the plugin with the given stdin and environment", func() {
+               _, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
+               Expect(err).NotTo(HaveOccurred())
+
+               debug, err := noop_debug.ReadDebug(debugFileName)
+               Expect(err).NotTo(HaveOccurred())
+               Expect(debug.Command).To(Equal("ADD"))
+               Expect(debug.CmdArgs.StdinData).To(Equal(stdin))
+               Expect(debug.CmdArgs.Netns).To(Equal("/some/netns/path"))
+       })
+
+       It("returns the resulting stdout as bytes", func() {
+               resultBytes, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
+               Expect(err).NotTo(HaveOccurred())
+
+               Expect(resultBytes).To(BeEquivalentTo(reportResult))
+       })
+
+       Context("when the Stderr writer is set", func() {
+               var stderrBuffer *bytes.Buffer
+
+               BeforeEach(func() {
+                       stderrBuffer = &bytes.Buffer{}
+                       execer.Stderr = stderrBuffer
+               })
+
+               It("forwards any stderr bytes to the Stderr writer", func() {
+                       _, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
+                       Expect(err).NotTo(HaveOccurred())
+
+                       Expect(stderrBuffer.String()).To(Equal("some stderr message"))
+               })
+       })
+
+       Context("when the plugin errors", func() {
+               BeforeEach(func() {
+                       debug.ReportError = "banana"
+                       Expect(debug.WriteDebug(debugFileName)).To(Succeed())
+               })
+
+               It("wraps and returns the error", func() {
+                       _, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
+                       Expect(err).To(HaveOccurred())
+                       Expect(err).To(MatchError("banana"))
+               })
+       })
+
+       Context("when the system is unable to execute the plugin", func() {
+               It("returns the error", func() {
+                       _, err := execer.ExecPlugin("/tmp/some/invalid/plugin/path", stdin, environ)
+                       Expect(err).To(HaveOccurred())
+                       Expect(err).To(MatchError(ContainSubstring("/tmp/some/invalid/plugin/path")))
+               })
+       })
+})
index 72eb837..7285878 100644 (file)
@@ -17,6 +17,7 @@ package invoke_test
 import (
        . "github.com/onsi/ginkgo"
        . "github.com/onsi/gomega"
+       "github.com/onsi/gomega/gexec"
 
        "testing"
 )
@@ -25,3 +26,20 @@ func TestInvoke(t *testing.T) {
        RegisterFailHandler(Fail)
        RunSpecs(t, "Invoke Suite")
 }
+
+const packagePath = "github.com/containernetworking/cni/plugins/test/noop"
+
+var pathToPlugin string
+
+var _ = SynchronizedBeforeSuite(func() []byte {
+       var err error
+       pathToPlugin, err = gexec.Build(packagePath)
+       Expect(err).NotTo(HaveOccurred())
+       return []byte(pathToPlugin)
+}, func(crossNodeData []byte) {
+       pathToPlugin = string(crossNodeData)
+})
+
+var _ = SynchronizedAfterSuite(func() {}, func() {
+       gexec.CleanupBuildArtifacts()
+})
index d9fb84c..71770bd 100644 (file)
@@ -26,6 +26,7 @@ import (
 type Debug struct {
        ReportResult string
        ReportError  string
+       ReportStderr string
        Command      string
        CmdArgs      skel.CmdArgs
 }
index d0db902..ddb1aca 100644 (file)
@@ -51,6 +51,8 @@ func debugBehavior(args *skel.CmdArgs, command string) error {
                return err
        }
 
+       os.Stderr.WriteString(debug.ReportStderr)
+
        if debug.ReportError != "" {
                return errors.New(debug.ReportError)
        } else {