Skip to content

Commit 62c66ea

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 62c66ea

4 files changed

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

0 commit comments

Comments
 (0)