Merge lp:~xavi-garcia-mena/go-unityscopes/utils-command into lp:go-unityscopes/v2

Proposed by Xavi Garcia
Status: Needs review
Proposed branch: lp:~xavi-garcia-mena/go-unityscopes/utils-command
Merge into: lp:go-unityscopes/v2
Diff against target: 304 lines (+287/-0)
3 files modified
utils/command/command.go (+109/-0)
utils/command/command_test.go (+144/-0)
utils/testutils/testutils.go (+34/-0)
To merge this branch: bzr merge lp:~xavi-garcia-mena/go-unityscopes/utils-command
Reviewer Review Type Date Requested Status
Unity API Team Pending
Review via email: mp+265832@code.launchpad.net

Commit message

Adding the initial utils directory to go-unityscopes.
It includes 2 packages:
* testutils
    With common test utilities
* command
    For executing system commands capturing the output, stderror and exit code.

Description of the change

Adding the initial utils directory to go-unityscopes.
It includes 2 packages:
* testutils
    With common test utilities
* command
    For executing system commands capturing the output, stderror and exit code.

To post a comment you must log in.

Unmerged revisions

70. By Xavi Garcia

Added utils directory with tools for building. Added common test utils package. Added utils command package to run system commands

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'utils'
2=== added directory 'utils/command'
3=== added file 'utils/command/command.go'
4--- utils/command/command.go 1970-01-01 00:00:00 +0000
5+++ utils/command/command.go 2015-07-24 14:19:30 +0000
6@@ -0,0 +1,109 @@
7+package command
8+
9+import (
10+ "bytes"
11+ "fmt"
12+ "os"
13+ "os/exec"
14+ "strings"
15+ "syscall"
16+)
17+
18+// Command represents a command to be executed, checking its exit code for errors
19+type Command struct {
20+ ExitCode int
21+ Cmd string
22+ Output string
23+}
24+
25+// BuildCommand splits the received string command and returns the first item (command) and the arguments.
26+func (c Command) BuildCommand(cmd string) (string, []string) {
27+ items := strings.Split(cmd, " ")
28+ return items[0], items[1:]
29+}
30+
31+// captureOutput is a helper method to capture stderr and, depending on the give boolean parameter, capures stdout as well
32+// Parameters:
33+// cmd: the command to capture from
34+// captureOutput: if true, captures the stdout, it prints the output to the system stdout otherwise
35+// Return: the stderr and stdout buffers created to capture the command output and errors.
36+func (c *Command) captureOutput(cmd *exec.Cmd, captureOutput bool) (*bytes.Buffer, *bytes.Buffer) {
37+ cmdStdErr := &bytes.Buffer{}
38+ cmdStdOut := &bytes.Buffer{}
39+ cmd.Stderr = cmdStdErr
40+ cmd.Stdin = os.Stdin
41+ if captureOutput == true {
42+ cmd.Stdout = cmdStdOut
43+ } else {
44+ cmd.Stdout = os.Stdout
45+ }
46+ return cmdStdOut, cmdStdErr
47+}
48+
49+// runCommand is the method that runs the command and checks the exit code.
50+// Parameters:
51+// name: system command to be executed
52+// captureOutput: if set to true it captures and stores the output, otherwise it uses os.Stdout
53+// arg: arguments to the command to be executed
54+//
55+// Return:
56+// If the exit code matches the expected nil is returned, otherwise it returns an error.
57+// If the command itself returns an error (command does not exists in the system or similar)
58+// it also returns an error.
59+func (c *Command) runCommand(name string, captureOutput bool, arg ...string) error {
60+ c.Cmd = fmt.Sprintf("%s %s", name, strings.Join(arg, " "))
61+ cmd := exec.Command(name, arg...)
62+ cmdStdOut, cmdStdErr := c.captureOutput(cmd, captureOutput)
63+ err := cmd.Run()
64+ errorMessage := ""
65+ c.Output = ""
66+ if err != nil {
67+ // retrieve exit code and error message
68+ if exitError, ok := err.(*exec.ExitError); ok {
69+ waitStatus := exitError.Sys().(syscall.WaitStatus)
70+ c.ExitCode = waitStatus.ExitStatus()
71+ // get the error message from the command stderr
72+ errorMessage = fmt.Sprintf("%s", cmdStdErr.Bytes())
73+ } else {
74+ // the command did not execute properly
75+ c.ExitCode = -1
76+ return fmt.Errorf("Error running command. %s", err.Error())
77+ }
78+ } else {
79+ c.ExitCode = 0
80+ }
81+ if captureOutput {
82+ c.Output = fmt.Sprintf("%s", cmdStdOut.Bytes())
83+ }
84+ if c.ExitCode != 0 {
85+ return fmt.Errorf("Error exit code %d. Error message: %s", c.ExitCode, errorMessage)
86+ } else {
87+ return nil
88+ }
89+}
90+
91+// Execute runs a system command and checks that its exit code is 0.
92+// Parameters:
93+// cmd: system command to be executed
94+//
95+// Return:
96+// If the exit code is 0 nil is returned, otherwise it returns an error.
97+// If the command itself returns an error (command does not exists in the system or similar)
98+// it also returns an error.
99+func (c *Command) Execute(cmd string) error {
100+ name, arg := c.BuildCommand(cmd)
101+ return c.runCommand(name, true, arg...)
102+}
103+
104+// ExecuteWithStdout runs a system command, checks that its exit code is 0, and prints the output of the command to stdout.
105+// Parameters:
106+// cmd: system command to be executed
107+//
108+// Return:
109+// If the exit code is 0 nil is returned, otherwise it returns an error.
110+// If the command itself returns an error (command does not exists in the system or similar)
111+// it also returns an error.
112+func (c *Command) ExecuteWithStdout(cmd string) error {
113+ name, arg := c.BuildCommand(cmd)
114+ return c.runCommand(name, false, arg...)
115+}
116
117=== added file 'utils/command/command_test.go'
118--- utils/command/command_test.go 1970-01-01 00:00:00 +0000
119+++ utils/command/command_test.go 2015-07-24 14:19:30 +0000
120@@ -0,0 +1,144 @@
121+package command_test
122+
123+import (
124+ . "gopkg.in/check.v1"
125+ "io/ioutil"
126+ "launchpad.net/go-unityscopes/v2/utils/command"
127+ "launchpad.net/go-unityscopes/v2/utils/testutils"
128+ "os"
129+ "testing"
130+)
131+
132+const (
133+ TEST_DIR = "testDir"
134+)
135+
136+type S struct{}
137+
138+func init() {
139+ Suite(&S{})
140+}
141+
142+func TestAll(t *testing.T) {
143+ TestingT(t)
144+}
145+
146+func setupTestDir() error {
147+ err := os.RemoveAll(TEST_DIR)
148+ if err != nil {
149+ return err
150+ }
151+ err = os.Mkdir(TEST_DIR, 0777)
152+ if err != nil {
153+ return err
154+ }
155+ err = ioutil.WriteFile("testDir/test_1.txt", []byte("blah"), 0777)
156+ if err != nil {
157+ return err
158+ }
159+ err = ioutil.WriteFile("testDir/test_2.txt", []byte("blah"), 0777)
160+ if err != nil {
161+ return err
162+ }
163+ return nil
164+}
165+
166+func tearDownTestDir() error {
167+ return os.RemoveAll(TEST_DIR)
168+}
169+
170+func (s *S) TestCommandIsExecutedOk(c *C) {
171+ err := setupTestDir()
172+ c.Assert(err, IsNil)
173+
174+ var cmd command.Command
175+ err = cmd.Execute("ls ./" + TEST_DIR)
176+ c.Check(err, IsNil)
177+ c.Check(cmd.Cmd, Equals, "ls ./"+TEST_DIR)
178+ c.Check(cmd.ExitCode, Equals, 0)
179+ c.Check(cmd.Output, Equals, ""+"test_1.txt\n"+"test_2.txt\n")
180+
181+ err = tearDownTestDir()
182+ c.Check(err, IsNil)
183+}
184+
185+func (s *S) TestCommandIsExecutedOkWithStdOut(c *C) {
186+ err := setupTestDir()
187+ c.Assert(err, IsNil)
188+
189+ var capture testutils.CaptureStdOut
190+ // capture stdout to instpect that the result of the print method is correct.
191+ capture.Capture()
192+
193+ var cmd command.Command
194+ err = cmd.ExecuteWithStdout("ls ./" + TEST_DIR)
195+ c.Check(err, IsNil)
196+ c.Check(cmd.Cmd, Equals, "ls ./"+TEST_DIR)
197+ c.Check(cmd.ExitCode, Equals, 0)
198+ c.Check(cmd.Output, Equals, "")
199+
200+ capture.StopCapture()
201+ c.Check(capture.GetCapuredOut(), Equals, ""+"test_1.txt\n"+"test_2.txt\n")
202+
203+ err = tearDownTestDir()
204+ c.Check(err, IsNil)
205+}
206+
207+func (s *S) TestCommandFailedBadParameters(c *C) {
208+ var cmd command.Command
209+ err := cmd.Execute("ls ./blah")
210+ c.Assert(err, Not(Equals), nil)
211+ c.Check(cmd.Cmd, Equals, "ls ./blah")
212+ c.Check(cmd.ExitCode, Equals, 2)
213+ c.Check(err.Error(), Equals, "Error exit code 2. Error message: ls: cannot access ./blah: No such file or directory\n")
214+}
215+
216+func (s *S) TestCommandFailedBadParametersWithStdout(c *C) {
217+ var cmd command.Command
218+ err := cmd.ExecuteWithStdout("ls ./blah")
219+ c.Assert(err, Not(Equals), nil)
220+ c.Check(cmd.Cmd, Equals, "ls ./blah")
221+ c.Check(cmd.ExitCode, Equals, 2)
222+ c.Check(cmd.Output, Equals, "")
223+ c.Check(err.Error(), Equals, "Error exit code 2. Error message: ls: cannot access ./blah: No such file or directory\n")
224+}
225+
226+func (s *S) TestCommandFailedCommandDoesNotExist(c *C) {
227+ var cmd command.Command
228+ err := cmd.Execute("lslslslslsnot-exists ./blah")
229+ c.Assert(err, Not(Equals), nil)
230+ c.Check(cmd.Cmd, Equals, "lslslslslsnot-exists ./blah")
231+ c.Check(cmd.ExitCode, Equals, -1)
232+ c.Check(err.Error(), Equals, "Error running command. exec: \"lslslslslsnot-exists\": executable file not found in $PATH")
233+}
234+
235+func (s *S) TestCommandFailedCommandDoesNotExistWithStdOut(c *C) {
236+ var cmd command.Command
237+ err := cmd.ExecuteWithStdout("lslslslslsnot-exists ./blah")
238+ c.Assert(err, Not(Equals), nil)
239+ c.Check(cmd.Cmd, Equals, "lslslslslsnot-exists ./blah")
240+ c.Check(cmd.ExitCode, Equals, -1)
241+ c.Check(cmd.Output, Equals, "")
242+ c.Check(err.Error(), Equals, "Error running command. exec: \"lslslslslsnot-exists\": executable file not found in $PATH")
243+}
244+
245+func (s *S) TestSplitCommandOK(c *C) {
246+ var cmd command.Command
247+ name, args := cmd.BuildCommand("ls -la ./")
248+ c.Check(name, Equals, "ls")
249+ c.Check(args, DeepEquals, []string{"-la", "./"})
250+}
251+
252+func (s *S) TestSplitCommandNoArgs(c *C) {
253+ var cmd command.Command
254+ name, args := cmd.BuildCommand("ls")
255+ c.Check(name, Equals, "ls")
256+ c.Check(args, DeepEquals, []string{})
257+}
258+
259+func (s *S) TestSplitCommandEmpty(c *C) {
260+ var cmd command.Command
261+ name, args := cmd.BuildCommand("")
262+ c.Check(name, Equals, "")
263+ c.Check(args, DeepEquals, []string{})
264+}
265
266=== added directory 'utils/testutils'
267=== added file 'utils/testutils/testutils.go'
268--- utils/testutils/testutils.go 1970-01-01 00:00:00 +0000
269+++ utils/testutils/testutils.go 2015-07-24 14:19:30 +0000
270@@ -0,0 +1,34 @@
271+package testutils
272+
273+import (
274+ "bytes"
275+ "io"
276+ "os"
277+)
278+
279+// CaptureStdOut is a struct that holds pointers to stdout and its redirection in order to capture stdout for some time.
280+type CaptureStdOut struct {
281+ StdOut *os.File
282+ NewOut *os.File
283+ NewReader *os.File
284+}
285+
286+// Capture captures the system stdout and stores it for restoring it in the future.
287+func (c *CaptureStdOut) Capture() {
288+ c.StdOut = os.Stdout
289+ c.NewReader, c.NewOut, _ = os.Pipe()
290+ os.Stdout = c.NewOut
291+}
292+
293+// StopCapture restores the system stdout
294+func (c *CaptureStdOut) StopCapture() {
295+ c.NewOut.Close()
296+ os.Stdout = c.StdOut
297+}
298+
299+// GetCapuredOut returns the stdout captured as a string.
300+func (c CaptureStdOut) GetCapuredOut() string {
301+ var buf bytes.Buffer
302+ io.Copy(&buf, c.NewReader)
303+ return buf.String()
304+}

Subscribers

People subscribed via source and target branches

to all changes: