Files
wehub-resource-sync 5357c39144
Fuzzer / Run Fuzzer (push) Has been cancelled
Race tests / Go race tests (ubuntu-22.04) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:01:40 +08:00

259 lines
11 KiB
Go

// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"os"
"os/exec"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"github.com/dolthub/dolt/go/libraries/doltcore/dconfig"
driver "github.com/dolthub/dolt/go/libraries/doltcore/dtestutils/sql_server_driver"
)
// These tests assert the behavior of an optimization where dolt CLI commands
// avoid loading potentially-large local database files when the command can
// instead be serviced by a running `dolt sql-server`.
//
// The hook for asserting that no table files are loaded is the
// DOLT_TEST_ASSERT_NO_TABLE_FILES_READ environment variable. When it is set,
// dolt panics if it ever bootstraps the chunk journal or opens a table file
// from disk. We use it to prove that, when a server is running, the CLI never
// touches the local database files.
//
// Every scenario uses a data dir root which contains multiple databases
// (db_one and db_two) as subdirectories, and exercises CLI invocations both
// from the data dir root and from within a particular database subdirectory.
// At the multi-db root there is no "current database", so the commands which
// require one (`dolt log`, `dolt remote -v`) are only exercised from the
// subdirectory; from the root we use `dolt sql -q` with database-qualified
// names.
//
// The test matrix has three top-level scenarios:
//
// 1. A dolt sql-server is running in the data dir root. The CLI is invoked
// with DOLT_TEST_ASSERT_NO_TABLE_FILES_READ=1 and must succeed, because it
// dispatches to the server without reading any table files.
// 2. No server is running, but a stale sql-server.info file is present at the
// data dir root. The CLI is invoked (executing locally) and must succeed.
// 3. No server is running, but a concurrent `dolt sql` process holds the
// write lock on db_one. The CLI is invoked (executing locally); reads
// succeed, writes to db_one fail because they cannot acquire the write
// lock, and writes to the unlocked db_two succeed.
//
// In scenarios 2 and 3 the CLI must read the local database, so we do not set
// DOLT_TEST_ASSERT_NO_TABLE_FILES_READ for those invocations.
// makePopulatedRepoStore creates a fresh repo store (the data dir root) with
// two initialized databases below it, each with a committed table, some rows,
// and a remote configured.
func makePopulatedRepoStore(t *testing.T, u driver.DoltUser) (driver.RepoStore, driver.Repo, driver.Repo) {
rs, err := u.MakeRepoStore()
require.NoError(t, err)
dbOne := makePopulatedDB(t, rs, "db_one")
dbTwo := makePopulatedDB(t, rs, "db_two")
return rs, dbOne, dbTwo
}
func makePopulatedDB(t *testing.T, rs driver.RepoStore, name string) driver.Repo {
repo, err := rs.MakeRepo(name)
require.NoError(t, err)
err = repo.DoltExec("sql", "-q", "create table vals (id int primary key auto_increment, v int);"+
"insert into vals (v) values (1), (2), (3);"+
"call dolt_commit('-Am', 'initial data')")
require.NoError(t, err)
require.NoError(t, repo.CreateRemote("origin", "file://"+filepath.Join(repo.Dir, "remote")))
return repo
}
// writeStaleInfoFile writes a stale sql-server.info file into the .dolt
// directory of the data dir root. Credentials lookup walks up from the cwd, so
// invocations from both the root and a database subdirectory will discover it.
func writeStaleInfoFile(t *testing.T, rs driver.RepoStore) {
dotDolt := filepath.Join(rs.Dir, ".dolt")
require.NoError(t, os.MkdirAll(dotDolt, 0755))
require.NoError(t, os.WriteFile(filepath.Join(dotDolt, "sql-server.info"), []byte("1:3306:this_is_not_a_real_secret"), 0600))
}
// withNoTableFilesRead sets DOLT_TEST_ASSERT_NO_TABLE_FILES_READ=1 on |cmd| so
// that the command panics if it loads any table files from disk.
func withNoTableFilesRead(cmd *exec.Cmd) *exec.Cmd {
cmd.Env = append(cmd.Env, dconfig.EnvAssertNoTableFilesRead+"=1")
return cmd
}
func assertCmdSucceeds(t *testing.T, cmd *exec.Cmd) string {
out, err := cmd.CombinedOutput()
require.NoError(t, err, "expected command to succeed, output:\n%s", string(out))
return string(out)
}
func assertCmdFails(t *testing.T, cmd *exec.Cmd) string {
out, err := cmd.CombinedOutput()
require.Error(t, err, "expected command to fail, output:\n%s", string(out))
return string(out)
}
func TestNoTableFilesRead(t *testing.T) {
t.Parallel()
u, err := driver.NewDoltUser()
require.NoError(t, err)
t.Cleanup(func() {
u.Cleanup()
})
// Sanity check: with the assertion hook enabled and no server running, CLI
// commands which read the database must fail. This proves the hook itself
// works and the success cases below are meaningful. We check both from the
// data dir root (database-qualified read) and from a database subdirectory.
t.Run("AssertHookFailsWhenLoadingTableFiles", func(t *testing.T) {
rs, dbOne, _ := makePopulatedRepoStore(t, u)
t.Run("from data dir root", func(t *testing.T) {
out := assertCmdFails(t, withNoTableFilesRead(rs.DoltCmd("sql", "-q", "select count(*) from db_one.vals")))
require.Regexp(t, "loading table files is disabled", out)
})
t.Run("from database subdirectory", func(t *testing.T) {
out := assertCmdFails(t, withNoTableFilesRead(dbOne.DoltCmd("log")))
require.Regexp(t, "loading table files is disabled", out)
})
})
// Scenario 1: a dolt sql-server is running in the data dir root. All CLI
// invocations set the assertion hook and must succeed by dispatching to the
// server.
t.Run("WithRunningServer", func(t *testing.T) {
rs, dbOne, _ := makePopulatedRepoStore(t, u)
var ports DynamicResources
ports.global = &GlobalPorts
ports.t = t
RunServerUntilEndOfTest(t, rs, &driver.Server{
Args: []string{"--port", `{{get_port "server"}}`},
DynamicPort: "server",
}, &ports)
t.Run("from data dir root", func(t *testing.T) {
t.Run("show databases", func(t *testing.T) {
out := assertCmdSucceeds(t, withNoTableFilesRead(rs.DoltCmd("sql", "-q", "show databases")))
require.Regexp(t, "db_one", out)
require.Regexp(t, "db_two", out)
})
t.Run("sql read", func(t *testing.T) {
out := assertCmdSucceeds(t, withNoTableFilesRead(rs.DoltCmd("sql", "-q", "select count(*) from db_one.vals")))
require.Regexp(t, "3", out)
})
t.Run("sql write of db_two", func(t *testing.T) {
assertCmdSucceeds(t, withNoTableFilesRead(rs.DoltCmd("sql", "-q", "insert into db_two.vals (v) values (10)")))
})
})
t.Run("from database subdirectory", func(t *testing.T) {
t.Run("sql read", func(t *testing.T) {
out := assertCmdSucceeds(t, withNoTableFilesRead(dbOne.DoltCmd("sql", "-q", "select count(*) from vals")))
require.Regexp(t, "3", out)
})
t.Run("sql write", func(t *testing.T) {
assertCmdSucceeds(t, withNoTableFilesRead(dbOne.DoltCmd("sql", "-q", "insert into vals (v) values (11)")))
})
t.Run("remote -v", func(t *testing.T) {
out := assertCmdSucceeds(t, withNoTableFilesRead(dbOne.DoltCmd("remote", "-v")))
require.Regexp(t, "origin", out)
})
t.Run("log", func(t *testing.T) {
out := assertCmdSucceeds(t, withNoTableFilesRead(dbOne.DoltCmd("log")))
require.Regexp(t, "initial data", out)
})
})
})
// Scenario 2: no server running, but a stale sql-server.info file is
// present at the data dir root. The CLI executes locally and must work.
t.Run("WithStaleInfoFile", func(t *testing.T) {
rs, dbOne, _ := makePopulatedRepoStore(t, u)
writeStaleInfoFile(t, rs)
t.Run("from data dir root", func(t *testing.T) {
t.Run("sql read", func(t *testing.T) {
out := assertCmdSucceeds(t, rs.DoltCmd("sql", "-q", "select count(*) from db_one.vals"))
require.Regexp(t, "3", out)
})
t.Run("sql write of db_two", func(t *testing.T) {
assertCmdSucceeds(t, rs.DoltCmd("sql", "-q", "insert into db_two.vals (v) values (20)"))
})
})
t.Run("from database subdirectory", func(t *testing.T) {
t.Run("sql read", func(t *testing.T) {
out := assertCmdSucceeds(t, dbOne.DoltCmd("sql", "-q", "select count(*) from vals"))
require.Regexp(t, "3", out)
})
t.Run("sql write", func(t *testing.T) {
assertCmdSucceeds(t, dbOne.DoltCmd("sql", "-q", "insert into vals (v) values (21)"))
})
t.Run("remote -v", func(t *testing.T) {
out := assertCmdSucceeds(t, dbOne.DoltCmd("remote", "-v"))
require.Regexp(t, "origin", out)
})
t.Run("log", func(t *testing.T) {
out := assertCmdSucceeds(t, dbOne.DoltCmd("log"))
require.Regexp(t, "initial data", out)
})
})
})
// Scenario 3: no server running, but a concurrent `dolt sql` process holds
// the write lock on db_one. The CLI executes locally: reads succeed, writes
// to db_one fail because they cannot acquire the exclusive write lock, and
// writes to the unlocked db_two succeed.
t.Run("WithConcurrentDoltSql", func(t *testing.T) {
rs, dbOne, _ := makePopulatedRepoStore(t, u)
RunDoltSQLUntilEndOfTest(t, dbOne)
t.Run("from data dir root", func(t *testing.T) {
t.Run("sql read of locked db_one", func(t *testing.T) {
out := assertCmdSucceeds(t, rs.DoltCmd("sql", "-q", "select count(*) from db_one.vals"))
require.Regexp(t, "3", out)
})
t.Run("sql write of locked db_one fails", func(t *testing.T) {
out := assertCmdFails(t, rs.DoltCmd("sql", "-q", "insert into db_one.vals (v) values (30)"))
require.Regexp(t, "(?i)read only", out)
})
t.Run("sql write of unlocked db_two succeeds", func(t *testing.T) {
assertCmdSucceeds(t, rs.DoltCmd("sql", "-q", "insert into db_two.vals (v) values (31)"))
})
})
t.Run("from database subdirectory", func(t *testing.T) {
t.Run("sql read", func(t *testing.T) {
out := assertCmdSucceeds(t, dbOne.DoltCmd("sql", "-q", "select count(*) from vals"))
require.Regexp(t, "3", out)
})
t.Run("sql write fails", func(t *testing.T) {
out := assertCmdFails(t, dbOne.DoltCmd("sql", "-q", "insert into vals (v) values (32)"))
require.Regexp(t, "(?i)read only", out)
})
t.Run("remote -v", func(t *testing.T) {
out := assertCmdSucceeds(t, dbOne.DoltCmd("remote", "-v"))
require.Regexp(t, "origin", out)
})
t.Run("log", func(t *testing.T) {
out := assertCmdSucceeds(t, dbOne.DoltCmd("log"))
require.Regexp(t, "initial data", out)
})
})
})
}