-
Notifications
You must be signed in to change notification settings - Fork 0
Load & integration testing for Raft #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature/raft-in-progress
Are you sure you want to change the base?
Conversation
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughIntroduces Ninja to Docker build. Updates standalone configs and Docker Compose to use absolute data path, service rename, and port 9891. Adjusts a C++ error message format. Adds a Go-based kvtest CLI scaffold, a core testing framework, a TinyKVPP gRPC adapter, and new proto definitions for TinyKVPP and Raft services. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant CLI as kvtest CLI
participant CMD as Cobra rootCmd
participant AD as TinyKVPPAdapter
participant GRPC as TinyKVPPService (gRPC)
Note over CLI,GRPC: New CLI + adapter flow
User->>CLI: Run kvtest
CLI->>CMD: Execute()
CMD->>AD: Connect(config)
alt Put
CMD->>AD: Put(key,value)
AD->>GRPC: Put(PutRequest)
GRPC-->>AD: PutResponse
AD-->>CMD: error/success
else Get
CMD->>AD: Get(key)
AD->>GRPC: Get(GetRequest)
GRPC-->>AD: GetResponse
AD-->>CMD: value/ErrKeyNotFound
end
CMD-->>CLI: Exit code
CLI-->>User: Output
sequenceDiagram
autonumber
participant Leader
participant Follower as RaftService (Follower)
rect rgba(230,240,255,0.5)
Note over Leader,Follower: Raft AppendEntries RPC (new proto)
Leader->>Follower: AppendEntries(req{term,prevLog,entries,leaderCommit})
Follower-->>Leader: AppendEntriesResponse{term,success,match_index}
end
Note over Leader,Follower: RequestVote and Replicate follow similar patterns
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@coderabbitai full review
- Framework core components in-progress - KVStore interface & implementation added - Config for the entire framework added
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
Dockerfile (1)
64-64
: ENTRYPOINT conflicts with docker-compose commandENTRYPOINT is "/app/tkvpp" but docker-compose also runs "/app/tkvpp" in command, resulting in duplicate binary in argv. Fix compose to only pass flags.
See compose diff under infra/docker-compose.standalone.yaml.
src/config.cpp (1)
177-200
: Fix fmt placeholder in logging error; prefer robust parsingThe else branch uses fmt::format with "%s" which won’t substitute loggingLevel. Use {} instead. Also consider spdlog::level::from_str to avoid string literal comparisons.
Apply:
- throw std::runtime_error(fmt::format("Unknown logging level: %s", loggingLevel)); + throw std::runtime_error(fmt::format("Unknown logging level: {}", loggingLevel));Optional parsing refactor:
void configureLogging(const std::string &loggingLevel) { - if (loggingLevel == SPDLOG_LEVEL_NAME_INFO) { - spdlog::set_level(spdlog::level::info); - } else if (loggingLevel == SPDLOG_LEVEL_NAME_DEBUG) { - spdlog::set_level(spdlog::level::debug); - } else if (loggingLevel == SPDLOG_LEVEL_NAME_TRACE) { - spdlog::set_level(spdlog::level::trace); - } else if (loggingLevel == SPDLOG_LEVEL_NAME_OFF) { - spdlog::set_level(spdlog::level::off); - } else { - throw std::runtime_error(fmt::format("Unknown logging level: {}", loggingLevel)); - } + auto lvl = spdlog::level::from_str(loggingLevel); + if (lvl == spdlog::level::off && loggingLevel != SPDLOG_LEVEL_NAME_OFF) { + // from_str returns off on failure; double-check to differentiate real "off" + throw std::runtime_error(fmt::format("Unknown logging level: {}", loggingLevel)); + } + spdlog::set_level(lvl); }
🧹 Nitpick comments (21)
tests/go/kvtest/cmd/root.go (4)
1-3
: Replace placeholder copyright.Update “NAME HERE ” with actual author or project copyright/license.
12-25
: Make root command user-friendly: show help by default and silence Cobra noise.Set a meaningful description, show help when no subcommands are provided, and silence usage/errors to avoid duplicate output.
Apply:
var rootCmd = &cobra.Command{ - Use: "kvtest", - Short: "A brief description of your application", - Long: `A longer description that spans multiple lines and likely contains -examples and usage of using your application. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, + Use: "kvtest", + Short: "CLI harness for TinyKVPP integration and load tests", + Long: "kvtest exercises TinyKVPP and Raft via gRPC. Use subcommands to run scenarios, load tests, and assertions.", + SilenceUsage: true, + SilenceErrors: true, + RunE: func(cmd *cobra.Command, args []string) error { + // Show help when no subcommands are specified + return cmd.Help() + }, }
6-10
: Print error before exiting.Currently Execute() exits with status 1 but doesn’t print the error, which hinders debugging in CI.
Apply:
import ( + "fmt" "os" "github.com/spf13/cobra" )
func Execute() { err := rootCmd.Execute() if err != nil { - os.Exit(1) + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) } }
36-46
: Replace placeholder flag with a meaningful endpoint flag aligned with defaults.A generic “toggle” flag isn’t actionable. Given the test harness context, an endpoint flag is more useful and matches the repo’s default port 9891.
Apply:
func init() { @@ - // Cobra also supports local flags, which will only run - // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + // Common endpoint for TinyKVPP gRPC server + rootCmd.PersistentFlags().StringP("endpoint", "e", "127.0.0.1:9891", "TinyKVPP gRPC endpoint (host:port)") }tests/go/kvtest/main.go (1)
1-5
: Replace placeholder copyright.Update “NAME HERE ” with real project metadata.
Dockerfile (1)
9-16
: Trim image size and harden apt usageConsider adding --no-install-recommends and noninteractive settings to reduce image size and avoid tzdata prompts in some bases.
Apply:
-RUN apt-get update && \ - apt-get -y install \ +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update && \ + apt-get -y install --no-install-recommends \ cmake \ ninja-build \ python3 \ python3-pip \ python3-virtualenv && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*infra/assets/tkvpp_config_standalone.json (1)
28-36
: Optional: align schema with config fieldsThe config carries "mode": "standalone" under database, but the JSON schema in src/config.cpp doesn’t define database.mode. Consider adding it (e.g., enum ["standalone","raft"]) to catch typos early.
src/config.cpp (2)
348-394
: Wrong error message for missing peersThe else branch throws an error about "id" when "peers" is missing. Fix the message.
Apply:
- throw std::runtime_error("\"id\" is not specified in the config"); + throw std::runtime_error("\"peers\" is not specified in the config");
107-115
: Tighten peers validation (host:port format, disallow 0.0.0.0)Currently peers are plain strings with no constraints. Consider a pattern like "host:port" and rejecting 0.0.0.0 via pattern or custom validation to prevent misconfiguration.
Example schema tweak:
"peers": { "type": "array", - "description": "Array of IPv4 addresses of peers", - "items": { "type": "string" } + "description": "Peer endpoints as host:port; do not use 0.0.0.0", + "items": { + "type": "string", + "pattern": "^(?!0\\.0\\.0\\.0:)[^:\\s]+:[0-9]{2,5}$" + } }infra/docker-compose.standalone.yaml (1)
5-7
: Unconfined seccomp reduces container isolationUnless you rely on syscalls blocked by the default profile, keep default seccomp. If needed, provide a minimal custom profile instead of unconfined.
Would you like a minimal seccomp profile template that permits only the required syscalls?
tests/go/kvtest/pkg/core/config.go (2)
23-30
: Nit: comment wording"AdapterConfig specified..." → "AdapterConfig specifies...".
Apply:
-// AdapterConfig specified which KV store adapter to use +// AdapterConfig specifies which KV store adapter to use
79-87
: Consider ramp-up and weights defaultsProviding non-zero RampUpTime and operation Weights helps produce smoother load profiles and reproducible mixes.
Apply:
Load: LoadConfig{ Enabled: true, Algorithm: "constant", StartUsers: 1, TargetUsers: 10, - Duration: 60 * time.Second, - ThinkTime: 100 * time.Microsecond, + Duration: 60 * time.Second, + RampUpTime: 5 * time.Second, + ThinkTime: 100 * time.Microsecond, + Weights: map[string]int{"read": 70, "write": 30}, },tests/go/kvtest/proto/tinykvpp/v1/tinykvpp_service.proto (2)
17-20
: Prefer gRPC status for errors; if keeping fields, rename for clarityReturning both success and error string is redundant and non-idiomatic in gRPC-Go. Prefer returning only the response payloads and convey failures via gRPC status codes. If you want to keep fields for this harness, consider renaming error -> message to avoid implying transport-level failure.
Example proto tweak (optional):
-message PutResponse { - bool success = 1; - string error = 2; -} +message PutResponse { + bool success = 1; // true if write succeeded + string message = 2; // optional context; not a transport error +}
26-29
: Consider bytes for values to allow binary payloadsIf keys/values may contain arbitrary bytes, strings can corrupt data. Using bytes makes the API future-proof even for non-UTF8 data.
-message GetResponse { - bool found = 1; - string value = 2; -} +message GetResponse { + bool found = 1; + bytes value = 2; +}(If you do this, update PutRequest.value to bytes too, and adjust the adapter accordingly.)
tests/go/kvtest/proto/raft/v1/raft_service.proto (2)
24-28
: Field numbering gap in LogEntry; either renumber now or reserve 3New file: safest is to use contiguous numbering. If 3 is intentionally removed, reserve it explicitly to avoid future collisions.
Option A — renumber now (preferred if no wire compatibility constraints yet):
message LogEntry { uint32 term = 1; uint32 index = 2; - bytes payload = 4; + bytes payload = 3; }Option B — keep as-is but reserve 3:
message LogEntry { uint32 term = 1; uint32 index = 2; + reserved 3; bytes payload = 4; }
61-69
: Replicate RPC: acceptable as a test hookGiven this is test harness-only, the shape is fine. Consider bytes for payloads if you foresee binary.
tests/go/kvtest/pkg/core/context.go (2)
7-11
: Private ctx field is unused; either expose it or remove itRight now ctx is dead weight. Expose it via a getter/setter or drop it to keep TestContext minimal.
Option A — provide accessors:
type TestContext struct { KVStore KVStoreInterface - ctx context.Context + ctx context.Context } +// Context returns the base context used by tests. +func (t TestContext) Context() context.Context { return t.ctx } + +// WithContext replaces the base context. +func (t *TestContext) WithContext(ctx context.Context) { t.ctx = ctx }Option B — remove ctx entirely if callers always pass their own contexts.
13-18
: Constructor returns by value; consider pointer if the context will mutateIf you plan to mutate internal state (e.g., swap ctx), returning a pointer avoids copies. If it stays immutable, returning by value is fine.
-func NewTestContext(adapter KVStoreInterface) TestContext { - return TestContext{ +func NewTestContext(adapter KVStoreInterface) *TestContext { + return &TestContext{ KVStore: adapter, ctx: context.Background(), } }tests/go/kvtest/pkg/core/interface.go (2)
10-22
: Interface shape is clear; consider adding Delete or dropping ErrDeleteFailedYou define ErrDeleteFailed but the interface has no Delete method. Either add Delete to the interface or remove the error to avoid dead code.
Option A — add Delete:
type KVStoreInterface interface { // Connection management Connect(config Config) error Close() error // CRUD operations Get(ctx context.Context, key []byte) ([]byte, error) Put(ctx context.Context, key, value []byte) error + Delete(ctx context.Context, key []byte) error // Testing utilities HealthCheck(ctx context.Context) error Name() string }
(Adapters/tests will need minor updates.)
Option B — remove ErrDeleteFailed if delete is out of scope for now.
34-39
: Predefined errors: consider simpler sentinelsUsing context.DeadlineExceeded/Canceled as inner causes may confuse callers. Consider basic sentinels (errors.New) and wrap transport errors at call sites.
If you agree, I can generate a follow-up patch replacing these with simple sentinels and updating adapter checks with errors.Is.
tests/go/kvtest/pkg/adapters/tinykvpp.go (1)
15-21
: Config has MaxRetries but no retry logic; either implement or dropIf you want basic resilience, add a bounded, jittered retry on Unavailable/DeadlineExceeded for Get/Put. Otherwise, remove MaxRetries to avoid confusion.
I can add a simple retry helper with exponential backoff guarded by context deadlines; say the word and I’ll draft it.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (3)
tests/go/kvtest/go.sum
is excluded by!**/*.sum
tests/go/kvtest/proto/tinykvpp/v1/tinykvpp_service.pb.go
is excluded by!**/*.pb.go
tests/go/kvtest/proto/tinykvpp/v1/tinykvpp_service_grpc.pb.go
is excluded by!**/*.pb.go
📒 Files selected for processing (13)
Dockerfile
(1 hunks)infra/assets/tkvpp_config_standalone.json
(2 hunks)infra/docker-compose.standalone.yaml
(1 hunks)src/config.cpp
(1 hunks)tests/go/kvtest/cmd/kvtest/main.go
(1 hunks)tests/go/kvtest/cmd/root.go
(1 hunks)tests/go/kvtest/main.go
(1 hunks)tests/go/kvtest/pkg/adapters/tinykvpp.go
(1 hunks)tests/go/kvtest/pkg/core/config.go
(1 hunks)tests/go/kvtest/pkg/core/context.go
(1 hunks)tests/go/kvtest/pkg/core/interface.go
(1 hunks)tests/go/kvtest/proto/raft/v1/raft_service.proto
(1 hunks)tests/go/kvtest/proto/tinykvpp/v1/tinykvpp_service.proto
(1 hunks)
🔇 Additional comments (15)
tests/go/kvtest/main.go (1)
9-11
: LGTM: main delegates to Cobra entrypoint.Clean handoff to cmd.Execute(); once the duplicate main is removed and root command wired, this is good to go.
Dockerfile (2)
11-11
: Ninja addition aligns with faster CMake buildsAdding ninja-build is a good move for faster incremental builds. Assuming your CMakePresets select the Ninja generator, this will reduce build times.
43-45
: Verify Ninja generator in CMake presetsI couldn’t locate a
CMakePresets.json
in the repository to confirm that theconan-release
andconan-debug
presets setgenerator = "Ninja"
. Please manually verify that these presets include:{ "name": "conan-release", …, "generator": "Ninja" }(and similarly for
conan-debug
). If they don’t, add-G Ninja
explicitly to thecmake --preset
invocation in your Dockerfile (Dockerfile lines 43–45):RUN cp -f ./build/CMakePresets.json . && \ cmake --preset conan-${BUILD_TYPE} && \ cmake --build ./build -t Main
infra/assets/tkvpp_config_standalone.json (1)
6-6
: Absolute data path matches container volumeSwitching database.path to /var/tkvpp is consistent with the Docker volume mount and the Dockerfile’s mkdir/chmod.
src/config.cpp (2)
158-159
: LGTM: fmt placeholder fixedSwapping %s for {} in the "Unable to open config file" error message correctly uses fmt formatting.
63-84
: Verify C++ mapping formaximumLevels
before updating schemaI searched for a corresponding C++ field or config‐struct for “maximumLevels” (e.g.
LSMTreeConfig
, anyfrom_json
/to_json
overloads, or direct references to"maximumLevels"
insrc/
) but didn’t find one. Before adding the new property to the JSON schema, please:• Confirm that your LSM configuration struct (whether named
LSMConfig
,LSMTreeConfig
, etc.) actually declares amaximumLevels
member (and thatfrom_json
/loader code reads it).
• If it doesn’t exist yet, add the field and mapping in the appropriate header/loader.
• Once the C++ side supports it, extend the schema insrc/config.cpp
as follows:"properties": { "flushThreshold": { "type": "integer", "description": "The threshold of bytes at which the memtable should be flushed", "minimum": 1 }, + "maximumLevels": { + "type": "integer", + "description": "Maximum number of LSM levels", + "minimum": 1 + }, "levelZeroCompaction": { "$ref": "#/$defs/compaction" },This ensures the schema’s
required
list aligns with both your examples (infra/assets/...
) and the actual C++ config handling.infra/docker-compose.standalone.yaml (2)
4-4
: Image name vs Dockerfile build targetCompose references image: tinykvpp-clang:latest, while Dockerfile defaults to TARGET=gcc. Ensure your CI publishes a clang-based image at this name or add a build section to compose with appropriate ARGs.
Suggested compose tweak if building locally:
- tkvpp-node-standalone: - image: tinykvpp-clang:latest + tkvpp-node-standalone: + build: + context: ../.. # adjust to repo root containing Dockerfile + dockerfile: Dockerfile + args: + TARGET: clang + image: tinykvpp-clang:latest
9-13
: Ports and volumes wiring looks consistent9891 maps correctly and the config/volume mounts align with the absolute data path /var/tkvpp.
tests/go/kvtest/pkg/core/config.go (2)
65-71
: Defaults align with infra (port 9891) and are sensibleType "tinykvpp" and Address "localhost:9891" mirror the standalone setup. Solid starting point.
88-99
: Scenario parameters are clear; keep ratios in sync with WeightsIf you adopt Weights above, ensure these ratios align with the same intended mix to avoid surprise in downstream load generators.
tests/go/kvtest/proto/tinykvpp/v1/tinykvpp_service.proto (1)
7-10
: Service surface LGTMSimple Put/Get for the test adapter is clear and minimal.
tests/go/kvtest/proto/raft/v1/raft_service.proto (2)
11-16
: Command enum LGTM
17-22
: NodeState enum LGTMtests/go/kvtest/pkg/adapters/tinykvpp.go (2)
120-122
: Name() LGTM
34-52
: Confirm grpc.NewClient() availability and consider switching to DialContextOur investigation shows:
- There is no
go.mod
in the repo root; however,tests/go/kvtest/go.sum
pinsgoogle.golang.org/grpc v1.74.2
(lines 53–54).- The adapter at
tests/go/kvtest/pkg/adapters/tinykvpp.go:42
is callinggrpc.NewClient(...)
, and there is no existing use ofgrpc.DialContext
in the codebase.Please verify the following:
• That the gRPC-Go version you’re actually using (e.g. v1.74.2) exports
grpc.NewClient
as a public, stable API. You can check this locally by running:go doc google.golang.org/grpc NewClient
or
godoc -http=:6060 # then open pkg/google.golang.org/grpc/ and search for NewClient
• If
NewClient
isn’t part of the stable API (or was introduced in a very recent release you’re not on), refactor the connection code to usegrpc.DialContext
with a timeout and optionalWithBlock()
, for example:if a.config.Address == "" { return fmt.Errorf("missing address") } ctx, cancel := context.WithTimeout(context.Background(), a.config.ConnectionTimeout) defer cancel() - conn, err := grpc.NewClient( - a.config.Address, - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) + conn, err := grpc.DialContext( + ctx, + a.config.Address, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithBlock(), + ) if err != nil { return fmt.Errorf("failed to connect to %s: %w", a.config.Address, err) }
"port": 9891, | ||
"id": 1, | ||
"peers": [ | ||
"0.0.0.0:8081" | ||
"0.0.0.0:9891" | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Peers should not use 0.0.0.0; make empty for standalone or use real endpoints
"0.0.0.0" is valid for binding but invalid as a remote peer address. In standalone mode, use an empty list; in clustered mode, list concrete host:port values for other nodes.
Apply:
- "peers": [
- "0.0.0.0:9891"
- ]
+ "peers": []
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
"port": 9891, | |
"id": 1, | |
"peers": [ | |
"0.0.0.0:8081" | |
"0.0.0.0:9891" | |
] | |
"port": 9891, | |
"id": 1, | |
"peers": [] |
🤖 Prompt for AI Agents
In infra/assets/tkvpp_config_standalone.json around lines 31 to 35, the peers
array contains "0.0.0.0:9891" which is incorrect for remote peer addresses;
update the peers field to be an empty array for standalone mode (i.e., "peers":
[]) or replace with concrete host:port entries when configuring a cluster, and
ensure the bind address remains the correct separate setting if needed.
security_opt: | ||
- seccomp:unconfined | ||
container_name: tkvpp-node-standalone | ||
command: ["/app/tkvpp", "-c", "/app/config.json"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t repeat the binary when image has ENTRYPOINT
With ENTRYPOINT ["/app/tkvpp"] in the image, your current command repeats the binary, leading to "/app/tkvpp /app/tkvpp -c ...". Pass only flags.
Apply:
- command: ["/app/tkvpp", "-c", "/app/config.json"]
+ command: ["-c", "/app/config.json"]
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
command: ["/app/tkvpp", "-c", "/app/config.json"] | |
command: ["-c", "/app/config.json"] |
🤖 Prompt for AI Agents
In infra/docker-compose.standalone.yaml around line 8 the docker-compose service
command redundantly repeats the image ENTRYPOINT ("/app/tkvpp"), resulting in
"/app/tkvpp /app/tkvpp -c ..."; change the command to pass only the
flags/arguments (e.g. "-c", "/app/config.json") so Docker uses the image
ENTRYPOINT with these flags rather than invoking the binary twice.
tests/go/kvtest/cmd/kvtest/main.go
Outdated
package main | ||
|
||
import () | ||
|
||
func main() {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Avoid duplicate entrypoints; keep only one main.
You now have two mains:
- tests/go/kvtest/main.go (calls cmd.Execute())
- tests/go/kvtest/cmd/kvtest/main.go (this file)
Pick a single layout:
- Option A (simplest): Keep tests/go/kvtest/main.go and delete tests/go/kvtest/cmd/kvtest/main.go.
- Option B (conventional multi-command): Move root.go under tests/go/kvtest/cmd/kvtest (package cmd), keep this main to call cmd.Execute(), and remove tests/go/kvtest/main.go.
Having both will confuse consumers and CI; with the current syntax error, the module cannot be built.
Run the following to enumerate all main packages under kvtest and confirm you have only one after the change:
🏁 Script executed:
#!/bin/bash
# List all Go files that declare package main under tests/go/kvtest
rg -n '^package main\b' -g 'tests/go/kvtest/**/*.go' -S -C1
Length of output: 247
Remove the duplicate main
entrypoint
The verification script still finds two package main
declarations under tests/go/kvtest
:
- tests/go/kvtest/main.go
- tests/go/kvtest/cmd/kvtest/main.go
You must choose one layout and delete the other to ensure there’s only a single executable entrypoint:
-
Option A (simplest):
• Keeptests/go/kvtest/main.go
(which callscmd.Execute()
).
• Deletetests/go/kvtest/cmd/kvtest/main.go
. -
Option B (conventional multi-command):
• Moveroot.go
undertests/go/kvtest/cmd/kvtest
(packagecmd
).
• Keeptests/go/kvtest/cmd/kvtest/main.go
to callcmd.Execute()
.
• Deletetests/go/kvtest/main.go
.
Maintaining both prevents the module from building and may confuse CI and downstream consumers. Please remove the unwanted main.go
.
🤖 Prompt for AI Agents
In tests/go/kvtest/cmd/kvtest/main.go (lines 1-5) there's a duplicate package
main entrypoint; remove this file so only tests/go/kvtest/main.go remains as the
single executable entrypoint (Option A). After deletion, ensure
tests/go/kvtest/main.go calls cmd.Execute() and no other package/main files
exist under tests/go/kvtest; run `go build` or the test verifier to confirm the
module builds.
tests/go/kvtest/cmd/kvtest/main.go
Outdated
@@ -0,0 +1,5 @@ | |||
package main | |||
|
|||
import () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix compile error: empty import block is invalid.
import ()
is a syntax error in Go (“expected import path, found ')'”). This file will not compile as-is.
If you intend to keep this entrypoint, wire it to Cobra and remove the empty import:
-package main
-
-import ()
-
-func main() {}
+package main
+
+import "github.com/lnikon/tinykvpp/tests/go/kvtest/cmd"
+
+func main() { cmd.Execute() }
Alternatively (recommended), delete this file entirely to avoid duplicate entrypoints (see next comment).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
import () | |
package main | |
import "github.com/lnikon/tinykvpp/tests/go/kvtest/cmd" | |
func main() { cmd.Execute() } |
🤖 Prompt for AI Agents
In tests/go/kvtest/cmd/kvtest/main.go around line 3 there is an empty import
block (`import ()`) which causes a Go compile error; either delete this file
entirely (preferred to avoid duplicate entrypoints) or replace the empty import
with the actual imports and wire the file into Cobra by adding the needed
imports and invoking the root command (e.g., import the cobra/root command
package and call its Execute in main) so the file becomes a valid entrypoint.
"github.com/lnikon/tinykvpp/tests/go/kvtest/pkg/core" | ||
pb "github.com/lnikon/tinykvpp/tests/go/kvtest/proto/tinykvpp/v1" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Import path for pb must match proto go_package
Once you fix go_package in tinykvpp_service.proto, ensure this import matches it. See the proto review for two consistent options.
Run this quick check in the repo to catch mismatches:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Proto go_package values:"
rg -nP 'option\s+go_package\s*=\s*"([^"]+)"' tests/go/kvtest/proto -n
echo
echo "pb imports in adapters:"
rg -nP '"github\.com/lnikon/tinykvpp/[^"]+/proto/tinykvpp/v1"' tests/go/kvtest/pkg -n
Length of output: 542
Adjust the pb
import to match the go_package
in your proto
The go_package
in tinykvpp_service.proto
is set to:
option go_package = "github.com/lnikon/tinykvpp/protos/tinykvpp/v1";
—but in tests/go/kvtest/pkg/adapters/tinykvpp.go
you’re importing:
pb "github.com/lnikon/tinykvpp/tests/go/kvtest/proto/tinykvpp/v1"
These must be identical. Update the import to:
pb "github.com/lnikon/tinykvpp/protos/tinykvpp/v1"
so that it matches the go_package
option exactly.
Locations to update:
- tests/go/kvtest/pkg/adapters/tinykvpp.go: line 12
Diff example:
- pb "github.com/lnikon/tinykvpp/tests/go/kvtest/proto/tinykvpp/v1"
+ pb "github.com/lnikon/tinykvpp/protos/tinykvpp/v1"
🤖 Prompt for AI Agents
In tests/go/kvtest/pkg/adapters/tinykvpp.go around lines 11 to 13, the protobuf
import alias `pb` points to the wrong module path (tests/go/kvtest/proto/...)
which does not match the `go_package` declared in the proto; update the `pb`
import to the exact go_package path
"github.com/lnikon/tinykvpp/protos/tinykvpp/v1" so the import matches the proto
option exactly and resolves package mismatches.
syntax = "proto3"; | ||
|
||
package raft.v1; | ||
|
||
service RaftService { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
❓ Verification inconclusive
Add go_package for Go codegen; without it, imports will be fragile
The Go plugin strongly recommends specifying go_package. Add one that matches where you’ll import the generated code.
syntax = "proto3";
package raft.v1;
+option go_package = "github.com/lnikon/tinykvpp/tests/go/kvtest/proto/raft/v1";
If you plan to place generated files under a different path, tell me the final location so I can align imports across the test harness.
Add a go_package
option for reliable Go imports
To ensure generated Go code imports consistently (and avoid fragile relative paths), specify the go_package
option in your .proto
file. For example, if your module path in go.mod
is github.com/lnikon/tinykvpp
and you’re placing the generated files under tests/go/kvtest/proto/raft/v1
, add:
File: tests/go/kvtest/proto/raft/v1/raft_service.proto
(lines 1–5)
syntax = "proto3";
package raft.v1;
+option go_package = "github.com/lnikon/tinykvpp/tests/go/kvtest/proto/raft/v1";
service RaftService {
• Verify that this import path matches exactly where you’ll generate the Go code.
• If you intend to generate into a different location, please share the final import path so we can align the go_package
value with your test harness.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
syntax = "proto3"; | |
package raft.v1; | |
service RaftService { | |
syntax = "proto3"; | |
package raft.v1; | |
option go_package = "github.com/lnikon/tinykvpp/tests/go/kvtest/proto/raft/v1"; | |
service RaftService { |
🤖 Prompt for AI Agents
tests/go/kvtest/proto/raft/v1/raft_service.proto lines 1-5: add a go_package
option to the .proto so generated Go imports are stable; set it to your module
path plus the generated path (for example use
github.com/lnikon/tinykvpp/tests/go/kvtest/proto/raft/v1 and append a short
package name after a semicolon, e.g. ;raftv1), ensuring the import path exactly
matches where you will generate the Go code or update the value to match the
final generation target.
message AppendEntriesRequest { | ||
uint32 term = 1; | ||
string leader_id = 2; | ||
uint32 prev_log_index = 3; | ||
uint32 prev_log_term = 4; | ||
repeated LogEntry entries = 5; | ||
uint32 leader_commit = 6; | ||
uint32 sender_id = 7; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
ID types are inconsistent; unify to a single type across requests/responses
leader_id is string here while sender_id is uint32; responses use responder_id uint32. Pick one type (string or uint32/uint64) and use it consistently across all messages. For Raft, numeric IDs (uint64) are common.
Example (numeric IDs):
message AppendEntriesRequest {
uint32 term = 1;
- string leader_id = 2;
+ uint32 leader_id = 2;
uint32 prev_log_index = 3;
uint32 prev_log_term = 4;
repeated LogEntry entries = 5;
uint32 leader_commit = 6;
uint32 sender_id = 7;
}
Apply the same choice to RequestVoteRequest and all responses.
Do you prefer uint64 for term/index/node IDs to avoid future overflow in long-running tests?
🏁 Script executed:
#!/bin/bash
set -e
proto_file="tests/go/kvtest/proto/raft/v1/raft_service.proto"
if [ ! -f "$proto_file" ]; then
echo "Error: $proto_file not found" >&2
exit 1
fi
echo "--- AppendEntriesRequest ---"
grep -n -C2 "message AppendEntriesRequest" "$proto_file" || true
echo
echo "--- RequestVoteRequest ---"
grep -n -C2 "message RequestVoteRequest" "$proto_file" || true
echo
echo "--- RequestVoteResponse ---"
grep -n -C2 "message RequestVoteResponse" "$proto_file" || true
echo
echo "--- AppendEntriesResponse ---"
grep -n -C2 "message AppendEntriesResponse" "$proto_file" || true
echo
echo "--- Occurrences of responder_id ---"
grep -n "responder_id" "$proto_file" || true
Length of output: 1387
Unify all node ID fields to a single numeric type
Across the four Raft RPC messages, the ID fields use mixed types— leader_id
is a string
, while sender_id
, candidate_id
, and both responder_id
fields are uint32
. We should pick one numeric type and apply it consistently. In Raft implementations it’s common to use 64-bit unsigned integers for node IDs (to avoid overflow in long-running clusters and tests).
Please update:
AppendEntriesRequest
:- change
string leader_id = 2;
→uint64 leader_id = 2;
- change
uint32 sender_id = 7;
→uint64 sender_id = 7;
- change
RequestVoteRequest
:- change
uint32 candidate_id = 2;
→uint64 candidate_id = 2;
- change
AppendEntriesResponse
:- change
uint32 responder_id = 4;
→uint64 responder_id = 4;
- change
RequestVoteResponse
:- change
uint32 responder_id = 3;
→uint64 responder_id = 3;
- change
Example diff for AppendEntriesRequest
:
message AppendEntriesRequest {
- uint32 term = 1;
- string leader_id = 2;
+ uint32 term = 1; // term/index fields left as uint32 for now
+ uint64 leader_id = 2; // unify node IDs to uint64
uint32 prev_log_index = 3;
uint32 prev_log_term = 4;
repeated LogEntry entries = 5;
uint32 leader_commit = 6;
- uint32 sender_id = 7;
+ uint64 sender_id = 7; // unified to uint64
}
Question: should we also bump term
, prev_log_index
, prev_log_term
, and leader_commit
from uint32
to uint64
now to fully future-proof against very large log sizes and term numbers?
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
message AppendEntriesRequest { | |
uint32 term = 1; | |
string leader_id = 2; | |
uint32 prev_log_index = 3; | |
uint32 prev_log_term = 4; | |
repeated LogEntry entries = 5; | |
uint32 leader_commit = 6; | |
uint32 sender_id = 7; | |
} | |
message AppendEntriesRequest { | |
uint32 term = 1; // term/index fields left as uint32 for now | |
uint64 leader_id = 2; // unify node IDs to uint64 | |
uint32 prev_log_index = 3; | |
uint32 prev_log_term = 4; | |
repeated LogEntry entries = 5; | |
uint32 leader_commit = 6; | |
uint64 sender_id = 7; // unified to uint64 | |
} |
message RequestVoteResponse { | ||
uint32 term = 1; | ||
uint32 vote_granted = 2; | ||
uint32 responder_id = 3; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vote_granted should be a bool, not uint32
This is a correctness bug; consumers will expect a boolean.
message RequestVoteResponse {
uint32 term = 1;
- uint32 vote_granted = 2;
+ bool vote_granted = 2;
uint32 responder_id = 3;
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
message RequestVoteResponse { | |
uint32 term = 1; | |
uint32 vote_granted = 2; | |
uint32 responder_id = 3; | |
} | |
message RequestVoteResponse { | |
uint32 term = 1; | |
bool vote_granted = 2; | |
uint32 responder_id = 3; | |
} |
🤖 Prompt for AI Agents
In tests/go/kvtest/proto/raft/v1/raft_service.proto around lines 55-59, the
RequestVoteResponse.message defines vote_granted as uint32 but it should be a
bool; change the field type to "bool vote_granted = 2;" (keep the field number 2
unchanged), then regenerate all protobuf artifacts (Go, any other language
bindings) and update any tests or call sites that set or read vote_granted to
use boolean values instead of integers to ensure consumers see the correct type.
|
||
package tinykvpp.v1; | ||
|
||
option go_package = "github.com/lnikon/tinykvpp/protos/tinykvpp/v1"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Align go_package with actual import path to avoid Go import mismatches
Adapter imports pb from tests/go/kvtest/proto/tinykvpp/v1, but go_package points to github.com/lnikon/tinykvpp/protos/tinykvpp/v1. This will generate code under a different module path than the one you import, leading to build errors.
Apply one of the following diffs (pick one and keep things consistent across repo):
Option A — keep current adapter import path:
-option go_package = "github.com/lnikon/tinykvpp/protos/tinykvpp/v1";
+option go_package = "github.com/lnikon/tinykvpp/tests/go/kvtest/proto/tinykvpp/v1";
Option B — move generated code to match go_package and update adapter import:
- pb "github.com/lnikon/tinykvpp/tests/go/kvtest/proto/tinykvpp/v1"
+ pb "github.com/lnikon/tinykvpp/protos/tinykvpp/v1"
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
option go_package = "github.com/lnikon/tinykvpp/protos/tinykvpp/v1"; | |
++ b/tests/go/kvtest/proto/tinykvpp/v1/tinykvpp_service.proto | |
@@ Lines 5-5 | |
option go_package = "github.com/lnikon/tinykvpp/tests/go/kvtest/proto/tinykvpp/v1"; |
// For internal database use | ||
message DatabaseOperation { | ||
enum Type { | ||
TYPE_UNSPECIFIED = 0; | ||
TYPE_PUT = 1; | ||
TYPE_DELETE = 2; | ||
TYPE_BATCH = 3; | ||
} | ||
|
||
Type type = 1; | ||
string key = 2; | ||
string value = 3; | ||
// Reserve 4 for Batch operations | ||
uint64 request_id = 5; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
❓ Verification inconclusive
Reserve field numbers explicitly and sketch batch extensibility
You skipped field 4 with a comment, but did not formally reserve it. Explicit reservations prevent accidental reuse and wire incompatibilities.
message DatabaseOperation {
enum Type {
TYPE_UNSPECIFIED = 0;
TYPE_PUT = 1;
TYPE_DELETE = 2;
TYPE_BATCH = 3;
}
Type type = 1;
string key = 2;
string value = 3;
- // Reserve 4 for Batch operations
+ // Reserved for future batch payload
+ reserved 4;
uint64 request_id = 5;
}
Optional: If batch is imminent, define it now to avoid another wire change:
message BatchOperation {
repeated DatabaseOperation ops = 1;
}
Would you like me to propagate the corresponding Go changes (adapter + tests) if you switch to bytes and/or add BatchOperation?
Add explicit reserved 4;
and sketch BatchOperation for forward‐compatibility
To avoid accidental reuse of tag 4 and ensure wire compatibility, please formally reserve it rather than just commenting it out:
• File: tests/go/kvtest/proto/tinykvpp/v1/tinykvpp_service.proto
• Location: inside message DatabaseOperation
, between fields 3 and 5
Proposed diff:
message DatabaseOperation {
enum Type {
TYPE_UNSPECIFIED = 0;
TYPE_PUT = 1;
TYPE_DELETE = 2;
TYPE_BATCH = 3;
}
Type type = 1;
string key = 2;
string value = 3;
- // Reserve 4 for Batch operations
+ // Reserved for future batch payload
+ reserved 4;
uint64 request_id = 5;
}
Optional enhancement: if you intend to implement batching soon, add a BatchOperation
now to avoid another wire‐breaking change:
message BatchOperation {
repeated DatabaseOperation ops = 1;
}
🤖 Prompt for AI Agents
In tests/go/kvtest/proto/tinykvpp/v1/tinykvpp_service.proto around lines 31-45,
the comment "Reserve 4 for Batch operations" should be implemented by formally
reserving tag 4 inside message DatabaseOperation to prevent reuse (add the
reserved 4; declaration between fields 3 and 5), and optionally add a new
message BatchOperation (containing a repeated DatabaseOperation field) after
DatabaseOperation so future batching uses a defined type instead of reclaiming
tag 4; ensure the .proto compiles and update any generated stubs if present.
- Add interface for scenarios - Implement CRUD scenario
- Add integration test executor
- Add test orchestrator
- Implement simple CRUD testing via kvtest - Fix memtables not being flushed
- Leftover cleanup
- Ignore binaries
- ReadYourWrites scenario in-progress
- ReadYourWrites scenario in-progress
- Implement basic memtable stress test - General refactoring
Summary by CodeRabbit
New Features
Chores
Style