diff --git a/internal/cmd/miniooni/session.go b/internal/cmd/miniooni/session.go index a56262c02b..e02fff21e9 100644 --- a/internal/cmd/miniooni/session.go +++ b/internal/cmd/miniooni/session.go @@ -9,6 +9,7 @@ import ( "github.com/apex/log" "github.com/ooni/probe-cli/v3/internal/engine" "github.com/ooni/probe-cli/v3/internal/kvstore" + "github.com/ooni/probe-cli/v3/internal/legacy/kvstore2dir" "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/runtimex" "github.com/ooni/probe-cli/v3/internal/version" @@ -27,9 +28,12 @@ func newSessionOrPanic(ctx context.Context, currentOptions *Options, proxyURL = mustParseURL(currentOptions.Proxy) } - kvstore2dir := filepath.Join(miniooniDir, "kvstore2") - kvstore, err := kvstore.NewFS(kvstore2dir) - runtimex.PanicOnError(err, "cannot create kvstore2 directory") + // We renamed kvstore2 to engine in the 3.20 development cycle + _ = kvstore2dir.Move(miniooniDir) + + enginedir := filepath.Join(miniooniDir, "engine") + kvstore, err := kvstore.NewFS(enginedir) + runtimex.PanicOnError(err, "cannot create engine directory") tunnelDir := filepath.Join(miniooniDir, "tunnel") err = os.MkdirAll(tunnelDir, 0700) diff --git a/internal/legacy/kvstore2dir/kvstore2dir.go b/internal/legacy/kvstore2dir/kvstore2dir.go new file mode 100644 index 0000000000..ef671a939d --- /dev/null +++ b/internal/legacy/kvstore2dir/kvstore2dir.go @@ -0,0 +1,34 @@ +// Package kvstore2dir migrates $OONI_HOME/kvstore2 to $OONI_HOME/engine. This ensures +// that miniooni and ooniprobe use the same directory name for the engine state. +package kvstore2dir + +import ( + "os" + "path/filepath" +) + +type statBuf interface { + IsDir() bool +} + +func simplifiedStat(path string) (statBuf, error) { + return os.Stat(path) +} + +var ( + osStat = simplifiedStat + osRename = os.Rename +) + +// Move moves $OONI_HOME/kvstore2 to $OONI_HOME/engine, if possible. +func Move(dir string) error { + kvstore2dir := filepath.Join(dir, "kvstore2") + if stat, err := osStat(kvstore2dir); err != nil || !stat.IsDir() { + return nil + } + enginedir := filepath.Join(dir, "engine") + if _, err := osStat(enginedir); err == nil { + return nil + } + return osRename(kvstore2dir, enginedir) +} diff --git a/internal/legacy/kvstore2dir/kvstore2dir_test.go b/internal/legacy/kvstore2dir/kvstore2dir_test.go new file mode 100644 index 0000000000..03f6b1276d --- /dev/null +++ b/internal/legacy/kvstore2dir/kvstore2dir_test.go @@ -0,0 +1,102 @@ +package kvstore2dir + +import ( + "errors" + "io" + "os" + "path/filepath" + "testing" +) + +type booleanStatBuf bool + +var _ statBuf = booleanStatBuf(true) + +// IsDir implements statBuf. +func (v booleanStatBuf) IsDir() bool { + return bool(v) +} + +func TestMove(t *testing.T) { + // testcase is a test case implemented by this function + type testcase struct { + name string + osStat func(name string) (statBuf, error) + osRename func(oldpath string, newpath string) error + expect error + } + + cases := []testcase{{ + name: "when we cannot stat kvstore2", + osStat: func(name string) (statBuf, error) { + return nil, io.EOF + }, + osRename: func(oldpath string, newpath string) error { + panic("should not be called") + }, + expect: nil, + }, { + name: "when kvstore2 is not a directory", + osStat: func(name string) (statBuf, error) { + return booleanStatBuf(false), nil + }, + osRename: func(oldpath string, newpath string) error { + panic("should not be called") + }, + expect: nil, + }, { + name: "when we can find kvstore2 as a dir and engine", + osStat: func(name string) (statBuf, error) { + if name == filepath.Join("xo", "kvstore2") { + return booleanStatBuf(true), nil + } + return booleanStatBuf(true), nil + }, + osRename: func(oldpath string, newpath string) error { + panic("should not be called") + }, + expect: nil, + }, { + name: "when we can find kvstore2 as a dir without engine", + osStat: func(name string) (statBuf, error) { + if name == filepath.Join("xo", "kvstore2") { + return booleanStatBuf(true), nil + } + return nil, io.EOF + }, + osRename: func(oldpath string, newpath string) error { + return nil + }, + expect: nil, + }} + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // override and restore functions + osStat = tc.osStat + osRename = tc.osRename + defer func() { + osStat = simplifiedStat + osRename = os.Rename + }() + + // invoke Move + err := Move("xo") + + // check the result + if !errors.Is(err, tc.expect) { + t.Fatal("expected", tc.expect, "got", err) + } + }) + } +} + +func TestSimplifiedStat(t *testing.T) { + buf, err := simplifiedStat("kvstore2dir.go") + if err != nil { + t.Fatal(err) + } + if buf.IsDir() { + t.Fatal("expected not dir") + } +}