Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df1d5cb8ff |
@@ -269,9 +269,10 @@ All three subcommands operate on a single SQLite database file:
|
||||
directory.
|
||||
- Each subcommand first prints one line on stderr naming the database it uses,
|
||||
before anything else it prints (progress displays included), whether or not
|
||||
stderr is a terminal. The line gives the path made absolute against the
|
||||
working directory, escaped like the paths in a report (see "Report output
|
||||
format"), and says where the path came from:
|
||||
stderr is a terminal. The line gives the path with the working directory put
|
||||
in front of it when it is relative, and otherwise as set (a `..` in it stays,
|
||||
so the line names the same file), escaped like the paths in a report (see
|
||||
"Report output format"), and says where the path came from:
|
||||
|
||||
```
|
||||
scan: new database /var/lib/sfdupes/db.sqlite (default)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -92,21 +93,30 @@ var errScanRunning = errors.New("another scan is running")
|
||||
|
||||
// databasePath resolves the database location: SFDUPES_DATABASE when
|
||||
// set and non-empty, the compiled-in default otherwise. It returns the
|
||||
// path made absolute against the working directory, and where the path
|
||||
// came from, "default" or "from SFDUPES_DATABASE", for the line each
|
||||
// subcommand prints first on stderr.
|
||||
// path, with the working directory put in front of it when it is
|
||||
// relative, and where the path came from, "default" or "from
|
||||
// SFDUPES_DATABASE", for the line each subcommand prints first on
|
||||
// stderr.
|
||||
func databasePath() (string, string, error) {
|
||||
path, source := defaultDatabasePath, "default"
|
||||
if p := os.Getenv(databaseEnv); p != "" {
|
||||
path, source = p, "from "+databaseEnv
|
||||
}
|
||||
|
||||
abs, err := filepath.Abs(path)
|
||||
if filepath.IsAbs(path) {
|
||||
return path, source, nil
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("resolve database path %s: %w", path, err)
|
||||
}
|
||||
|
||||
return abs, source, nil
|
||||
// Not filepath.Abs or filepath.Join: they clean the path, which
|
||||
// drops each ".." together with the directory name before it. When
|
||||
// that directory is a symlink, the cleaned path names another file.
|
||||
// The working directory ends in "/" only when it is the root.
|
||||
return strings.TrimSuffix(wd, "/") + "/" + path, source, nil
|
||||
}
|
||||
|
||||
// scanParams are the connection parameters for scan: read-write, with
|
||||
|
||||
+16
-2
@@ -45,7 +45,8 @@ func openTestDB(t *testing.T) *sql.DB {
|
||||
//nolint:paralleltest // changes the process-wide working directory
|
||||
func TestDatabasePath(t *testing.T) {
|
||||
// README §Database: SFDUPES_DATABASE when set and non-empty, the
|
||||
// default otherwise, made absolute against the working directory.
|
||||
// default otherwise, with the working directory put in front of a
|
||||
// relative path. Nothing is cleaned, so a ".." stays.
|
||||
dir := t.TempDir()
|
||||
t.Chdir(dir)
|
||||
|
||||
@@ -55,7 +56,10 @@ func TestDatabasePath(t *testing.T) {
|
||||
}{
|
||||
{"", defaultDatabasePath, "default"},
|
||||
{"/custom/place.sqlite", "/custom/place.sqlite", fromEnv},
|
||||
{"sub/place.sqlite", filepath.Join(dir, "sub/place.sqlite"), fromEnv},
|
||||
{"/custom/link/../place.sqlite", "/custom/link/../place.sqlite",
|
||||
fromEnv},
|
||||
{"sub/place.sqlite", dir + "/sub/place.sqlite", fromEnv},
|
||||
{"link/../place.sqlite", dir + "/link/../place.sqlite", fromEnv},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -67,6 +71,16 @@ func TestDatabasePath(t *testing.T) {
|
||||
databaseEnv, tc.env, path, source, err, tc.path, tc.source)
|
||||
}
|
||||
}
|
||||
|
||||
// In the root directory the path gets one slash in front, not two.
|
||||
t.Chdir("/")
|
||||
t.Setenv(databaseEnv, "place.sqlite")
|
||||
|
||||
path, _, err := databasePath()
|
||||
if err != nil || path != "/place.sqlite" {
|
||||
t.Errorf("databasePath() in / = %q, %v; want %q",
|
||||
path, err, "/place.sqlite")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenScanDatabaseCreates(t *testing.T) {
|
||||
|
||||
@@ -352,6 +352,72 @@ func TestRunNamesRelativeDatabase(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunUsesDatabaseThroughSymlinkedDirectory(t *testing.T) {
|
||||
// README §Database: the path names the file exactly. With link a
|
||||
// symlink to the directory real/sub, link/../db.sqlite names
|
||||
// real/db.sqlite, not db.sqlite beside link. Every subcommand must
|
||||
// use that file, relative or absolute, and its first line must name
|
||||
// the path as set, with the working directory put in front of a
|
||||
// relative one.
|
||||
dir := t.TempDir()
|
||||
t.Chdir(dir)
|
||||
|
||||
err := os.MkdirAll(filepath.Join(dir, "real", "sub"), 0o750)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = os.Symlink(filepath.Join(dir, "real", "sub"),
|
||||
filepath.Join(dir, "link"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
named := dir + "/link/../db.sqlite"
|
||||
from := " (from " + databaseEnv + ")\n"
|
||||
tree := t.TempDir()
|
||||
|
||||
t.Setenv(databaseEnv, "link/../db.sqlite")
|
||||
|
||||
want := "scan: new database " + named + from
|
||||
if got := scanOK(t, tree); !strings.HasPrefix(got, want) {
|
||||
t.Errorf("relative scan stderr = %q, want it to start %q", got, want)
|
||||
}
|
||||
|
||||
// The absolute path names the database the relative one created.
|
||||
t.Setenv(databaseEnv, named)
|
||||
|
||||
want = "scan: database " + named + from
|
||||
if got := scanOK(t, tree); !strings.HasPrefix(got, want) {
|
||||
t.Errorf("absolute scan stderr = %q, want it to start %q", got, want)
|
||||
}
|
||||
|
||||
for _, name := range []string{cmdReport, cmdTrees} {
|
||||
stderr := captureStderr(t)
|
||||
|
||||
code := run([]string{name}, io.Discard, os.Stderr)
|
||||
|
||||
want = name + ": database " + named + from
|
||||
if got := stderr(); code != exitOK || !strings.HasPrefix(got, want) {
|
||||
t.Errorf("run(%s) = %d with stderr %q, want %d and it to "+
|
||||
"start %q", name, code, got, exitOK, want)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = os.Stat(filepath.Join(dir, "real", "db.sqlite"))
|
||||
if err != nil {
|
||||
t.Errorf("database the path names: %v", err)
|
||||
}
|
||||
|
||||
// With no file at the cleaned path, report and trees succeeded on
|
||||
// the database in real.
|
||||
_, err = os.Stat(filepath.Join(dir, "db.sqlite"))
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
t.Errorf("database at the cleaned path: stat error %v, want it "+
|
||||
"missing", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunNamesDefaultDatabase(t *testing.T) {
|
||||
// README §Database: with SFDUPES_DATABASE empty, each subcommand's
|
||||
// first line on stderr names the default database. A database may
|
||||
|
||||
Reference in New Issue
Block a user