Add sfdupes info: show the database's details (closes #117)
check / check (push) Waiting to run
check / check (push) Waiting to run
info prints the database's path and source, the sizes of the database file and its -wal file, when the database file last changed, the schema version and the record counts, one per line as a name, a tab and the value. It opens the database as report does and shows an open and a count phase on stderr. The schema version and the counts come from one read-only transaction, the counts from one query. seedDatabase now closes test databases out of WAL mode, as scan does; otherwise info's own connection creates an empty -wal file. The open phase's name and text became constants for the linter, and the scan command moved to newScanCommand to keep newRootCommand short enough. Judgement call: the lines are written after the last display ends, so they skip the display's stdout writer. Model: opus-5-5
This commit is contained in:
+24
-14
@@ -284,10 +284,16 @@ func assertFatalOutput(t *testing.T, stderr, stdout string) {
|
||||
}
|
||||
|
||||
func TestRunMissingDatabaseIsFatal(t *testing.T) {
|
||||
// README §Database: report and trees need an existing database; a
|
||||
// missing one exits 1 with a message telling the user to run scan,
|
||||
// after the line naming the database.
|
||||
for _, name := range []string{cmdReport, cmdTrees} {
|
||||
// README §Database: report, trees and info need an existing
|
||||
// database; a missing one exits 1 with a message telling the user to
|
||||
// run scan, after the line naming the database.
|
||||
openLines := map[string]string{
|
||||
cmdReport: reportOpenLine,
|
||||
cmdTrees: treesOpenLine,
|
||||
cmdInfo: infoOpenLine,
|
||||
}
|
||||
|
||||
for name, openLine := range openLines {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
path := testDBPath(t)
|
||||
t.Setenv(databaseEnv, path)
|
||||
@@ -305,13 +311,8 @@ func TestRunMissingDatabaseIsFatal(t *testing.T) {
|
||||
|
||||
// Each shows its open phase, as it starts and as it ends,
|
||||
// before the error.
|
||||
opening := reportOpenLine + "\n" + reportOpenLine + "\n"
|
||||
if name == cmdTrees {
|
||||
opening = treesOpenLine + "\n" + treesOpenLine + "\n"
|
||||
}
|
||||
|
||||
want := name + ": database " + path + " (from " + databaseEnv +
|
||||
")\n" + opening +
|
||||
")\n" + openLine + "\n" + openLine + "\n" +
|
||||
"sfdupes: " + path + ": no database (run \"sfdupes " +
|
||||
"scan\" first, or set " + databaseEnv + ")\n"
|
||||
if got := stderr(); got != want {
|
||||
@@ -512,9 +513,9 @@ func TestRunNamesDefaultDatabase(t *testing.T) {
|
||||
// exist at the default path on the machine running the test, so
|
||||
// nothing here may create or read one: scan gets an operand that
|
||||
// does not exist, which stops it before it takes the lock, and
|
||||
// report and trees a cancelled context, which stops them before
|
||||
// they read anything. report and trees show their open phase after
|
||||
// the line.
|
||||
// report, trees and info a cancelled context, which stops them
|
||||
// before they read anything. They show their open phase after the
|
||||
// line.
|
||||
t.Setenv(databaseEnv, "")
|
||||
|
||||
// Whether scan calls the database new depends on that machine too.
|
||||
@@ -552,6 +553,13 @@ func TestRunNamesDefaultDatabase(t *testing.T) {
|
||||
want: "trees: database " + defaultDatabasePath + " (default)\n" +
|
||||
treesOpenLine + "\n" + treesOpenLine + "\n",
|
||||
},
|
||||
cmdInfo: {
|
||||
run: func() error {
|
||||
return runInfo(cancelledContext(t), io.Discard)
|
||||
},
|
||||
want: "info: database " + defaultDatabasePath + " (default)\n" +
|
||||
infoOpenLine + "\n" + infoOpenLine + "\n",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
@@ -578,6 +586,7 @@ func TestRunUsageErrors(t *testing.T) {
|
||||
"scan without paths": {[]string{cmdScan}, usageMarker},
|
||||
"report with args": {[]string{cmdReport, "x"}, usageMarker},
|
||||
"trees with args": {[]string{cmdTrees, "x"}, usageMarker},
|
||||
"info with args": {[]string{cmdInfo, "x"}, usageMarker},
|
||||
"unknown flag": {[]string{cmdScan, "--nope", "/"}, usageMarker},
|
||||
"unknown subcommand": {[]string{"nope"}, "unknown command"},
|
||||
}
|
||||
@@ -1258,7 +1267,7 @@ func TestRunReportsDuringScan(t *testing.T) {
|
||||
func TestRunStdoutClosedIsFatal(t *testing.T) {
|
||||
// README §Error handling: a stdout write failure exits 1, reported
|
||||
// in one line on stderr.
|
||||
for _, name := range []string{cmdReport, cmdTrees} {
|
||||
for _, name := range []string{cmdReport, cmdTrees, cmdInfo} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Setenv(databaseEnv, testDBPath(t))
|
||||
|
||||
@@ -1308,6 +1317,7 @@ func TestStdoutWriteErrorPropagates(t *testing.T) {
|
||||
cases := map[string]func(context.Context, io.Writer) error{
|
||||
cmdReport: runReport,
|
||||
cmdTrees: runTrees,
|
||||
cmdInfo: runInfo,
|
||||
}
|
||||
|
||||
for name, fn := range cases {
|
||||
|
||||
Reference in New Issue
Block a user