Skip to content

Commit 6e01e12

Browse files
kanard38knard38
authored andcommitted
DAOS-18304 ddb: Add Go unit tests for ddb command layer
Add Go unit test coverage for the ddb command layer, made possible by the DdbAPI interface introduced in the previous patch. New files: - test_helpers.go: DdbContextStub implementing DdbAPI for use in tests without a live VOS environment; captureStdout, runCmdToStdout, runMainFlow and isArgEqual test utilities. - ddb_commands_test.go: - TestHelpCmds: verifies help output for the ls and open commands. - TestCmds: argument and option parsing for ls (path, --recursive, --details) and open (--vos_path, --db_path, --write_mode). - TestManPage: man page output to stdout and to file. - main_test.go: - TestParseOpts: general help, unknown commands with --help, default option values, flag parsing (--debug, --write, --vos_path, --db_path, --version) and error cases (conflicting --cmd / --cmd_file, missing --vos_path when --db_path is set). - TestRun: version output, unknown command detection, auto-open behaviour with --vos_path and --db_path, command-line and command-file execution modes. - TestStrToLogLevels: all supported log level string conversions. Features: recovery Signed-off-by: Cedric Koch-Hofer <cedric.koch-hofer@hpe.com>
1 parent f81f3be commit 6e01e12

4 files changed

Lines changed: 1042 additions & 9 deletions

File tree

src/control/cmd/ddb/command_completers_test.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
//
2+
// (C) Copyright 2026 Hewlett Packard Enterprise Development LP
3+
//
4+
// SPDX-License-Identifier: BSD-2-Clause-Patent
5+
//
6+
17
package main
28

39
import (
@@ -18,7 +24,7 @@ func createFile(t *testing.T, filePath string) {
1824

1925
fd, err := os.Create(filePath)
2026
if err != nil {
21-
t.Fatalf("Failed to create test vos file %s: %v", filePath, err)
27+
t.Fatalf("failed to create test vos file %s: %v", filePath, err)
2228
}
2329
fd.Close()
2430
}
@@ -27,14 +33,14 @@ func createDirAll(t *testing.T, dirPath string) {
2733
t.Helper()
2834

2935
if err := os.MkdirAll(dirPath, 0755); err != nil {
30-
t.Fatalf("Failed to create test pool directory %s: %v", dirPath, err)
36+
t.Fatalf("failed to create test pool directory %s: %v", dirPath, err)
3137
}
3238
}
3339

34-
func testSetup(t *testing.T) (tmpDir string, teardown func()) {
40+
func testSetup(t *testing.T) string {
3541
t.Helper()
3642

37-
tmpDir, teardown = test.CreateTestDir(t)
43+
tmpDir := t.TempDir()
3844

3945
for _, dir := range testPoolDirs {
4046
createDirAll(t, filepath.Join(tmpDir, dir))
@@ -51,12 +57,11 @@ func testSetup(t *testing.T) (tmpDir string, teardown func()) {
5157
createDirAll(t, filepath.Join(tmpDir, "bar", "baz"))
5258
createFile(t, filepath.Join(tmpDir, "bar", "baz", "no_vos"))
5359

54-
return
60+
return tmpDir
5561
}
5662

5763
func TestListVosFiles(t *testing.T) {
58-
tmpDir, teardown := testSetup(t)
59-
t.Cleanup(teardown)
64+
tmpDir := testSetup(t)
6065

6166
for name, tc := range map[string]struct {
6267
args string
@@ -118,7 +123,7 @@ func TestListVosFiles(t *testing.T) {
118123
} {
119124
t.Run(name, func(t *testing.T) {
120125
results := listVosFiles(tc.args)
121-
test.AssertStringsEqual(t, tc.expRes, results, "listDirVos results do not match expected")
126+
test.AssertStringsEqual(t, tc.expRes, results, "unexpected listVosFiles results")
122127
})
123128
}
124129
}
@@ -169,7 +174,7 @@ func TestFilterSuggestions(t *testing.T) {
169174
} {
170175
t.Run(name, func(t *testing.T) {
171176
results := filterSuggestions(tc.prefix, initialSuggestions, additionalSuggestions)
172-
test.AssertStringsEqual(t, tc.expRes, results, "filterSuggestions results do not match expected")
177+
test.AssertStringsEqual(t, tc.expRes, results, "unexpected filterSuggestions results")
173178
})
174179
}
175180
}
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
//
2+
// (C) Copyright 2026 Hewlett Packard Enterprise Development LP
3+
//
4+
// SPDX-License-Identifier: BSD-2-Clause-Patent
5+
//
6+
7+
package main
8+
9+
import (
10+
"fmt"
11+
"os"
12+
"path"
13+
"path/filepath"
14+
"strings"
15+
"testing"
16+
17+
"github.com/daos-stack/daos/src/control/common/test"
18+
)
19+
20+
func runHelpCmd(t *testing.T, cmdStr string, helpSubStr string) {
21+
t.Helper()
22+
23+
var ctx DdbContextStub
24+
25+
// Create a temporary config file with the help command
26+
tmpCfgDir := t.TempDir()
27+
tmpCfgFile := path.Join(tmpCfgDir, "ddb-cmd_file.txt")
28+
if err := os.WriteFile(tmpCfgFile, []byte(fmt.Sprintf("%s --help", cmdStr)), 0644); err != nil {
29+
t.Fatalf("failed to write temp config file: %v", err)
30+
}
31+
32+
// Run the help command with a command file
33+
args := test.JoinArgs(nil, "--cmd_file="+tmpCfgFile)
34+
stdoutCmdFile, err := runMainFlow(&ctx, args)
35+
if err != nil {
36+
t.Fatalf("unexpected error when running '%s --help' via command file: want nil, got %v", cmdStr, err)
37+
}
38+
test.AssertTrue(t, strings.Contains(stdoutCmdFile, helpSubStr),
39+
fmt.Sprintf("expected stdout to contain %q: got\n%s", helpSubStr, stdoutCmdFile))
40+
41+
// Run the help command with a command line
42+
args = test.JoinArgs(nil, cmdStr, "--help")
43+
stdoutCmdLine, err := runMainFlow(&ctx, args)
44+
if err != nil {
45+
t.Fatalf("unexpected error when running '%s --help' via command line: want nil, got %v", cmdStr, err)
46+
}
47+
test.AssertTrue(t, strings.Contains(stdoutCmdLine, helpSubStr),
48+
fmt.Sprintf("expected stdout to contain %q: got\n%s", helpSubStr, stdoutCmdLine))
49+
50+
// Compare command line and command file outputs
51+
test.AssertEqual(t, stdoutCmdFile, stdoutCmdLine,
52+
fmt.Sprintf("unexpected help output mismatch between command file and command line for '%s'", cmdStr))
53+
}
54+
55+
func TestHelpCmds(t *testing.T) {
56+
for name, tc := range map[string]struct {
57+
cmdStr string
58+
helpSubStr string
59+
}{
60+
"help for 'ls' command": {
61+
cmdStr: "ls",
62+
helpSubStr: "Usage:\n ls [flags] [path]\n",
63+
},
64+
"help for 'open' command": {
65+
cmdStr: "open",
66+
helpSubStr: "Usage:\n open [flags] path\n",
67+
},
68+
} {
69+
t.Run(name, func(t *testing.T) {
70+
runHelpCmd(t, tc.cmdStr, tc.helpSubStr)
71+
})
72+
}
73+
}
74+
75+
func TestCmds(t *testing.T) {
76+
for name, tc := range map[string]struct {
77+
args []string
78+
expCalls func(ctx *DdbContextStub)
79+
expStdout []string
80+
expErr error
81+
}{
82+
"ls invalid options": {
83+
args: []string{"ls", "--bar"},
84+
expErr: ddbTestErr("invalid flag: --bar"),
85+
},
86+
"ls default": {
87+
args: []string{"ls"},
88+
expCalls: func(ctx *DdbContextStub) {
89+
ctx.ls = func(path string, recursive bool, details bool) error {
90+
fmt.Println("ls called")
91+
if err := isArgEqual("", path, "path"); err != nil {
92+
return err
93+
}
94+
if err := isArgEqual(false, recursive, "recursive"); err != nil {
95+
return err
96+
}
97+
if err := isArgEqual(false, details, "details"); err != nil {
98+
return err
99+
}
100+
return nil
101+
}
102+
},
103+
expStdout: []string{"ls called"},
104+
},
105+
"ls path": {
106+
args: []string{"ls", "/[0]"},
107+
expCalls: func(ctx *DdbContextStub) {
108+
ctx.ls = func(path string, recursive bool, details bool) error {
109+
fmt.Println("ls called")
110+
if err := isArgEqual("/[0]", path, "path"); err != nil {
111+
return err
112+
}
113+
if err := isArgEqual(false, recursive, "recursive"); err != nil {
114+
return err
115+
}
116+
if err := isArgEqual(false, details, "details"); err != nil {
117+
return err
118+
}
119+
return nil
120+
}
121+
},
122+
expStdout: []string{"ls called"},
123+
},
124+
"ls long recursive opt": {
125+
args: []string{"ls", "--recursive"},
126+
expCalls: func(ctx *DdbContextStub) {
127+
ctx.ls = func(path string, recursive bool, details bool) error {
128+
fmt.Println("ls called")
129+
if err := isArgEqual("", path, "path"); err != nil {
130+
return err
131+
}
132+
if err := isArgEqual(true, recursive, "recursive"); err != nil {
133+
return err
134+
}
135+
if err := isArgEqual(false, details, "details"); err != nil {
136+
return err
137+
}
138+
return nil
139+
}
140+
},
141+
expStdout: []string{"ls called"},
142+
},
143+
"ls short details opt": {
144+
args: []string{"ls", "-d"},
145+
expCalls: func(ctx *DdbContextStub) {
146+
ctx.ls = func(path string, recursive bool, details bool) error {
147+
fmt.Println("ls called")
148+
if err := isArgEqual("", path, "path"); err != nil {
149+
return err
150+
}
151+
if err := isArgEqual(false, recursive, "recursive"); err != nil {
152+
return err
153+
}
154+
if err := isArgEqual(true, details, "details"); err != nil {
155+
return err
156+
}
157+
return nil
158+
}
159+
},
160+
expStdout: []string{"ls called"},
161+
},
162+
"ls details long opt": {
163+
args: []string{"ls", "--details"},
164+
expCalls: func(ctx *DdbContextStub) {
165+
ctx.ls = func(path string, recursive bool, details bool) error {
166+
fmt.Println("ls called")
167+
if err := isArgEqual("", path, "path"); err != nil {
168+
return err
169+
}
170+
if err := isArgEqual(false, recursive, "recursive"); err != nil {
171+
return err
172+
}
173+
if err := isArgEqual(true, details, "details"); err != nil {
174+
return err
175+
}
176+
return nil
177+
}
178+
},
179+
expStdout: []string{"ls called"},
180+
},
181+
} {
182+
t.Run(name, func(t *testing.T) {
183+
checkCmd := func(t *testing.T, stdout string, err error) {
184+
t.Helper()
185+
test.CmpErr(t, tc.expErr, err)
186+
if tc.expErr != nil {
187+
return
188+
}
189+
for _, msg := range tc.expStdout {
190+
test.AssertTrue(t, strings.Contains(stdout, msg),
191+
fmt.Sprintf("expected stdout to contain %q: got\n%s", msg, stdout))
192+
}
193+
}
194+
195+
t.Run("command-line", func(t *testing.T) {
196+
var ctx DdbContextStub
197+
if tc.expCalls != nil {
198+
tc.expCalls(&ctx)
199+
}
200+
stdout, err := runMainFlow(&ctx, tc.args)
201+
checkCmd(t, stdout, err)
202+
})
203+
204+
t.Run("command-file", func(t *testing.T) {
205+
tmpDir := t.TempDir()
206+
cmdFile := filepath.Join(tmpDir, "cmds.txt")
207+
cmdLine := strings.Join(tc.args, " ")
208+
if err := os.WriteFile(cmdFile, []byte(cmdLine), 0644); err != nil {
209+
t.Fatalf("failed to write command file: %v", err)
210+
}
211+
var ctx DdbContextStub
212+
if tc.expCalls != nil {
213+
tc.expCalls(&ctx)
214+
}
215+
stdout, err := runMainFlow(&ctx, []string{"--cmd_file=" + cmdFile})
216+
checkCmd(t, stdout, err)
217+
})
218+
})
219+
}
220+
}
221+
222+
func TestManPage(t *testing.T) {
223+
for name, tc := range map[string]struct {
224+
args []string
225+
expStdout []string
226+
expErr error
227+
}{
228+
"manpage to stdout contains sections and commands": {
229+
args: []string{"manpage"},
230+
expStdout: []string{
231+
manArgsHeader,
232+
manCmdsHeader,
233+
manPathSection[:20],
234+
manMdOnSsdSection[:20],
235+
manLoggingSection[:20],
236+
// Known commands must appear in the listing
237+
".B ls\n",
238+
".B open\n",
239+
},
240+
},
241+
} {
242+
t.Run(name, func(t *testing.T) {
243+
var ctx DdbContextStub
244+
stdout, err := runMainFlow(&ctx, tc.args)
245+
test.CmpErr(t, tc.expErr, err)
246+
if tc.expErr != nil {
247+
return
248+
}
249+
for _, msg := range tc.expStdout {
250+
test.AssertTrue(t, strings.Contains(stdout, msg),
251+
fmt.Sprintf("expected stdout to contain %q: got\n%s", msg, stdout))
252+
}
253+
})
254+
}
255+
256+
// Test --output flag: man page is written to a file, stdout is empty.
257+
t.Run("manpage to file", func(t *testing.T) {
258+
tmpDir := t.TempDir()
259+
outFile := filepath.Join(tmpDir, "ddb.groff")
260+
261+
var ctx DdbContextStub
262+
stdout, err := runMainFlow(&ctx, []string{"manpage", "--output=" + outFile})
263+
if err != nil {
264+
t.Fatalf("unexpected error when running 'manpage --output': want nil, got %v", err)
265+
}
266+
test.AssertTrue(t, stdout == "",
267+
fmt.Sprintf("expected empty stdout when --output is set: got\n%s", stdout))
268+
269+
content, err := os.ReadFile(outFile)
270+
if err != nil {
271+
t.Fatalf("failed to read output file: %v", err)
272+
}
273+
for _, section := range []string{manArgsHeader, manCmdsHeader, manLoggingSection[:20]} {
274+
test.AssertTrue(t, strings.Contains(string(content), section),
275+
fmt.Sprintf("expected file to contain %q: got\n%s", section, string(content)))
276+
}
277+
})
278+
}

0 commit comments

Comments
 (0)