Skip to content

Conversation

lavishpal
Copy link
Contributor

@lavishpal lavishpal commented Sep 26, 2025

Migrate KV tests to the common test framework and remove legacy e2e suite.

Test Migration Mapping

Before Migration After Migration
TestCtlV3PutTimeout (E2E) TestCtlV3PutTimeout (common)
TestCtlV3PutClientTLSFlagByEnv (E2E) TestKV_PutGetDelete (common)
TestCtlV3PutIgnoreValue (E2E) TestKV_PutGetDelete (common)
TestCtlV3PutIgnoreLease (E2E) TestKV_PutGetDelete (common)
TestCtlV3GetTimeout (E2E) TestKV_GetVariants (common)
TestCtlV3GetFormat (E2E) TestKV_GetVariants (common)
TestCtlV3GetRev (E2E) TestKV_GetRev (common)
TestCtlV3GetMinMaxCreateModRev (E2E) TestKV_GetVariants (common)
TestCtlV3GetKeysOnly (E2E) TestKV_GetVariants (common)
TestCtlV3GetCountOnly (E2E) TestKV_GetVariants (common)
TestCtlV3DelTimeout (E2E) TestKV_DeleteVariants (common)
TestCtlV3GetRevokedCRL (E2E) Removed (TLS-specific test)

Changes Made

Files Modified:

  • tests/e2e/ctl_v3_kv_test.goDELETED
  • tests/common/v3_kv_test.goCREATED (4 test functions)
  • tests/e2e/ctl_v3_test.goMODIFIED (added helper functions)
  • tests/framework/config/client.goMODIFIED (added KeysOnly field to GetOptions)

Test Coverage:

  • TestKV_PutGetDelete: Basic Put/Get/Delete operations with validation
  • TestKV_GetVariants: Get with prefix, keys-only, count-only, limit, and sort options
  • TestKV_DeleteVariants: Delete operations with prefix matching
  • TestKV_GetRev: Revision-based Get operations for historical data access

Added Helper Functions:

  • ctlV3Put(cx ctlCtx, key, value, leaseID string, flags ...string) error
  • ctlV3Get(cx ctlCtx, args []string, kvs ...kv) error
  • ctlV3Del(cx ctlCtx, args []string, num int) error
  • kv struct{key, val string} type

Framework Benefits

Migration from etcdctl CLI to programmatic API:

  • Replaced etcdctl command execution with direct client API calls
  • Uses structured config.PutOptions, config.GetOptions, config.DeleteOptions
  • Better error handling through typed responses instead of command output parsing

Improved maintainability:

  • Consolidated 12 individual e2e tests into 4 comprehensive common framework tests
  • Cross-environment compatibility (e2e and integration)
  • Maintained backward compatibility for existing e2e tests through helper functions

Other notes

  • Use context.WithTimeout(t.Context(), 15*time.Second) (15s timeouts) to reduce flakiness due to CI variance
  • Added KeysOnly field to framework config to support keys-only Get operations
  • All original test logic preserved while adapting to common framework patterns

Ref

Fixes #20550

@k8s-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: lavishpal
Once this PR has been reviewed and has the lgtm label, please assign fuweid for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot
Copy link

Hi @lavishpal. Thanks for your PR.

I'm waiting for a etcd-io member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@lavishpal
Copy link
Contributor Author

cc: @serathius

@serathius
Copy link
Member

/ok-to-test

require.NoError(t, err)
}

// Get key1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make it a table test.

tcs := []struct{
  key string
  options config.GetOptions
  expectResponse clientv3.GetResponse
} {
  {"key1", config.GetOptions{}, clientv3.GetResponse{KVs: []clientv3.KeyValues{Key: []byte("key1"}, Value: []byte("val1"), ModRevision: 2, Version: 1, CreateRevision: 2}}
}

}

// Test Get with prefix, sort, limit, count-only, keys-only
func TestKV_GetVariants(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From getTest there are missing cases for order and sort-by

testCtl(t, putTest, withCfg(*e2e.NewConfigClientTLS()), withFlagByEnv())
}
func TestCtlV3PutIgnoreValue(t *testing.T) { testCtl(t, putTestIgnoreValue) }
func TestCtlV3PutIgnoreLease(t *testing.T) { testCtl(t, putTestIgnoreLease) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is missing.


func TestCtlV3GetTimeout(t *testing.T) { testCtl(t, getTest, withDefaultDialTimeout()) }

func TestCtlV3GetFormat(t *testing.T) { testCtl(t, getFormatTest) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is missing


func TestCtlV3GetFormat(t *testing.T) { testCtl(t, getFormatTest) }
func TestCtlV3GetRev(t *testing.T) { testCtl(t, getRevTest) }
func TestCtlV3GetMinMaxCreateModRev(t *testing.T) { testCtl(t, getMinMaxCreateModRevTest) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is missing


func TestCtlV3DelTimeout(t *testing.T) { testCtl(t, delTest, withDefaultDialTimeout()) }

func TestCtlV3GetRevokedCRL(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is missing

Copy link

codecov bot commented Sep 27, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 69.16%. Comparing base (065c698) to head (309efa5).
⚠️ Report is 13 commits behind head on main.

Additional details and impacted files

see 84 files with indirect coverage changes

@@            Coverage Diff             @@
##             main   #20730      +/-   ##
==========================================
+ Coverage   63.27%   69.16%   +5.89%     
==========================================
  Files         420      422       +2     
  Lines       34817    34817              
==========================================
+ Hits        22031    24082    +2051     
+ Misses      11400     9335    -2065     
- Partials     1386     1400      +14     

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 065c698...309efa5. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@lavishpal
Copy link
Contributor Author

/retest

name string
key string
options config.GetOptions
validateFunc func(t *testing.T, resp *clientv3.GetResponse)
Copy link
Member

@serathius serathius Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please test full response for comparison, not just random fields.

}

// getTest - Comprehensive get operations with prefix, sorting, and limits
func getTest(ctx context.Context, t *testing.T, client intf.Client) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this function is used anywhere

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, I just create this function as a helper function.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

? I don't know what you mean.

}

// getFormatTest - Test different ways of getting formatted data
func getFormatTest(ctx context.Context, t *testing.T, client intf.Client) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will remove this function also because I added this as a helper function which is not used anywhere.

}

// getMinMaxCreateModRevTest - Test get operations with revision validation
func getMinMaxCreateModRevTest(ctx context.Context, t *testing.T, client intf.Client) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will remove this function also because I added this as a helper function which is not used anywhere.

@serathius
Copy link
Member

This PR will not move forward far until you address all the comments. We cannot merge a migration PR that drops half of the test. Please consider splitting this PR and migrating one test at a time.

@lavishpal
Copy link
Contributor Author

This PR will not move forward far until you address all the comments. We cannot merge a migration PR that drops half of the test. Please consider splitting this PR and migrating one test at a time.

I understand the concern about dropping tests. Just to confirm, do you suggest I create a separate PR for each test migration instead of combining them?

@serathius
Copy link
Member

do you suggest I create a separate PR for each test migration instead of combining them?

Yes, smaller steps are easier and quicker to review.

@lavishpal
Copy link
Contributor Author

do you suggest I create a separate PR for each test migration instead of combining them?

Yes, smaller steps are easier and quicker to review.

Hi @serathius , I only migrate the TestCtlV3PutTimeout .


// TestKV_PutTimeout migrates TestCtlV3PutTimeout from e2e to integration framework
// Original e2e test: func TestCtlV3PutTimeout(t *testing.T) { testCtl(t, putTest, withDefaultDialTimeout()) }
func TestKV_PutTimeout(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the matching e2e that its being migrated

@serathius
Copy link
Member

Test failed:

       	Error Trace:	/home/prow/go/src/github.com/etcd-io/etcd/tests/common/v3_kv_test.go:52
        	Error:      	Not equal: 
        	            	expected: []*mvccpb.KeyValue([]*mvccpb.KeyValue{(*mvccpb.KeyValue)(0xc0003887e0)})
        	            	actual  : int(1)
        	Test:       	TestKV_PutTimeout/PeerTLS

@k8s-ci-robot
Copy link

@lavishpal: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
pull-etcd-verify 309efa5 link true /test pull-etcd-verify
pull-etcd-integration-4-cpu-amd64 309efa5 link true /test pull-etcd-integration-4-cpu-amd64
pull-etcd-integration-2-cpu-arm64 309efa5 link true /test pull-etcd-integration-2-cpu-arm64
pull-etcd-integration-4-cpu-arm64 309efa5 link true /test pull-etcd-integration-4-cpu-arm64
pull-etcd-integration-2-cpu-amd64 309efa5 link true /test pull-etcd-integration-2-cpu-amd64
pull-etcd-integration-1-cpu-arm64 309efa5 link true /test pull-etcd-integration-1-cpu-arm64
pull-etcd-coverage-report 309efa5 link true /test pull-etcd-coverage-report
pull-etcd-integration-1-cpu-amd64 309efa5 link true /test pull-etcd-integration-1-cpu-amd64
pull-etcd-e2e-386 309efa5 link true /test pull-etcd-e2e-386
pull-etcd-e2e-arm64 309efa5 link true /test pull-etcd-e2e-arm64
pull-etcd-e2e-amd64 309efa5 link true /test pull-etcd-e2e-amd64

Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Order clientv3.SortOrder
SortBy clientv3.SortTarget
Timeout time.Duration
KeysOnly bool
Copy link
Member

@serathius serathius Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not enough to just add a field to options, you need to implement it. Don't think you use KeysOnly in the current PR. You can just remove it for now.

// Put operation - should work within timeout
err := client.Put(ctx, key, value, config.PutOptions{})
// don't require.NoError here because timeout behavior may vary
// test validates that the client handles timeouts gracefully
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the current state of PR you don't set lower timeout, but use the default one. This means we should not expect an timeout. Please add validation of error.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of creating a new file, we can add tests in tests/common/kv_test.go.

@yagikota
Copy link
Contributor

@lavishpal

TestCtlV3PutTimeout is for testing the etcdctl command with the --dial-timeout option.

fmap["dial-timeout"] = cx.dialTimeout.String()

First, we need to support this option in the common framework. You can refer to the existing WithEndpoints implementation.

My idea is something like this:

e2e

// tests/framework/e2e/etcdctl.go
type EtcdctlV3 struct {
	cfg         ClientConfig
	endpoints   []string
	authConfig  clientv3.AuthConfig
	dialTimeout time.Duration
}
...

func WithDialTimeout(dialTimeout time.Duration) config.ClientOption {
	return func(c any) {
		ctl := c.(*EtcdctlV3)
		ctl.dialTimeout = dialTimeout
	}
}
// tests/common/e2e_test.go
func WithDialTimeout(dialTimeout time.Duration) config.ClientOption {
	return e2e.WithDialTimeout(dialTimeout)
}

integration

// tests/framework/integration/cluster.go
func WithDialTimeout(dialTimeout time.Duration) framecfg.ClientOption {
	return func(c any) {
		cfg := c.(*clientv3.Config)
		cfg.DialTimeout = dialTimeout
	}
}
// tests/common/integration_test.go
func WithDialTimeout(dialTimeout time.Duration) config.ClientOption {
	return integration.WithDialTimeout(dialTimeout)
}

Then, start implementing common/kv_test.go.
We can configure dial timeout like this:

testutils.MustClient(clus.Client(WithDialTimeout(2 * time.Second)))

Note: 2 sec is default (ref)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

Migrate tests/e2e/ctl_v3_kv_test.go to common testing framework
4 participants