-
Notifications
You must be signed in to change notification settings - Fork 9
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
Centralize Go test suite #48
Conversation
|
||
namespace, err := uuid.NewV7() |
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.
Just changed this to make it more up to date w/current Go code I was seeing online
pinecone/index_connection_test.go
Outdated
namespace, err := uuid.NewV7() | ||
assert.NoError(ts.T(), err) | ||
namespace, err := uuid.NewUUID() | ||
require.NoError(ts.T(), err) |
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.
Changed some assert
s to require
s since that's what I was seeing is more common with testify
.
.github/workflows/ci.yaml
Outdated
@@ -17,7 +17,8 @@ jobs: | |||
run: | | |||
go get ./pinecone | |||
- name: Run tests | |||
run: go test ./pinecone | |||
# run: go test -count=1 -v ./pinecone |
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.
We will re-enable this (and remove the line below) once the tests for client
are working
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.
Would it make sense to just do that in this PR as well rather than having tests commented out between cycles?
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.
Yeah -- lemme rebase your changes (where you added the new env vars for the client
integration tests) into mine once you merge, and then it'll be done
pinecone/index_connection_test.go
Outdated
if err != nil { | ||
t.FailNow() | ||
} | ||
client, err := NewClient(NewClientParams{ApiKey: apiKey, Headers: map[string]string{"content-type": "application/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.
Had to add new header for content-type
. Not entirely sure why but the backend was yelling at me that it wasn't set before this line.
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.
We shouldn't need to do this here. It's confusing to me that we'd need to when all we did was change test code rather than the implementation code in the SDK.
What was the backend yelling at you? This header should be handled by the underlying generated code for all requests where it's necessary. Look at all the places it's applied in control_plane.opas.go
:
return NewCreateCollectionRequestWithBody(server, "application/json", bodyReader) |
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.
Yeah it was yelling at me that the content type header was missing, but lemme try again -- maybe that was a red herring for something else at the time
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.
I've removed and CI still passes... I'm not sure why this is happening but it's not happening any longer.
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.
Took a first pass, nice work getting us onto the path of having our integration tests fully isolated! I think the most important feedback here is around allowing the tests to generate index names, and then rely on those for the test run and cleanup. I'd prefer to move away from storing index names in environment variables if possible.
pinecone/index_connection_test.go
Outdated
if err != nil { | ||
t.FailNow() | ||
} | ||
client, err := NewClient(NewClientParams{ApiKey: apiKey, Headers: map[string]string{"content-type": "application/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.
We shouldn't need to do this here. It's confusing to me that we'd need to when all we did was change test code rather than the implementation code in the SDK.
What was the backend yelling at you? This header should be handled by the underlying generated code for all requests where it's necessary. Look at all the places it's applied in control_plane.opas.go
:
return NewCreateCollectionRequestWithBody(server, "application/json", bodyReader) |
.github/workflows/ci.yaml
Outdated
@@ -17,7 +17,8 @@ jobs: | |||
run: | | |||
go get ./pinecone | |||
- name: Run tests | |||
run: go test ./pinecone | |||
# run: go test -count=1 -v ./pinecone |
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.
Would it make sense to just do that in this PR as well rather than having tests commented out between cycles?
.github/workflows/ci.yaml
Outdated
TEST_PODS_INDEX_NAME: ${{ secrets.TEST_PODS_INDEX_NAME }} | ||
TEST_SERVERLESS_INDEX_NAME: ${{ secrets.TEST_SERVERLESS_INDEX_NAME }} |
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.
The strings that these secrets represent changed in GitHub for this repository, right?
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.
Correct
pinecone/index_connection_test.go
Outdated
fmt.Printf("Creating Serverless index: %s\n", idxName) | ||
serverlessIdx, err := in.CreateServerlessIndex(ctx, &CreateServerlessIndexRequest{ | ||
Name: idxName, | ||
Dimension: int32(setDimensionsForTestIndexes()), |
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.
Are we not able to just use Dimension: 5
here? Like here:
go-pinecone/pinecone/client_test.go
Line 572 in be9fe92
Dimension: 10, |
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.
I just use this new helper function int mult places, so I thought it was better to call a function than to hardcode it to a number
pinecone/index_connection_test.go
Outdated
return array | ||
} | ||
|
||
func getStatus(ts *IndexConnectionTestsIntegration, ctx context.Context) (bool, error) { |
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.
getStatus
of what? This should probably be more descriptive. Since it's operating on indexes depending on which is targeted via ts.IndexType
and then polling for Ready
and a boolean I'd try and make that more clear.
pinecone/index_connection_test.go
Outdated
return desc.Status.Ready, nil | ||
} | ||
|
||
func upsert(ts *IndexConnectionTestsIntegration, ctx context.Context, vectors []*Vector) error { |
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.
Same as above regarding naming.
}) | ||
assert.NoError(ts.T(), err) | ||
|
||
time.Sleep(5 * time.Second) |
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.
Has 5 seconds worked well for this so far? We had use retries and longer wait windows in some other tests to handle upsert and update. Just curious how it's performed for you so far.
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.
Yeah I tried longer and shorter, but 5 was the shortest I found where it still passed
func generateFloat32Array(n int) []float32 { | ||
array := make([]float32, n) | ||
for i := 0; i < n; i++ { | ||
array[i] = float32(i) |
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.
This is minor - would be nice to generate random floats or ints for these helpers rather than just using the same index values every time. I guess it ultimately doesn't matter much, but random values could be a nice stress test.
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.
Will do!
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.
I tried doing it by setting the start of the range to a random int, but because n
is always so low (5
or less), it often fails. I think we can just leave as is?
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.
We can leave as-is, it's nitpicky.
I think you could write a helper function that uses math/rand
maybe:
func RandomFloat32() float32 {
rand.Seed(time.Now().UnixNano()) // Seed the random number generator
return rand.Float32()
}
pinecone/index_connection_test.go
Outdated
} | ||
client, err := NewClient(NewClientParams{ApiKey: apiKey, Headers: map[string]string{"content-type": "application/json"}}) | ||
require.NotNil(t, client, "Client should not be nil after creation") | ||
require.NoError(t, err) | ||
|
||
podIndexName := os.Getenv("TEST_PODS_INDEX_NAME") |
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.
Rather than hard-coding this name into an invisible secret in GitHub, I'd prefer if we could generate a random name to use each run since the suite is managing it's resources anyways.
If we hard-code things like this we'd also run into problems if we want to ever run integration tests in parallel, because the tests are no longer isolated and are all pointing at this predefined set of index names.
Can we look at adding a helper function that generates a random name, possibly with a seed string? Something like we do in our other test suites:
- TypeScript: https://github.com/pinecone-io/pinecone-ts-client/blob/b5a66e6216d7615576dd881a67db627be2aa7bba/src/integration/test-helpers.ts#L92
- Python: https://github.com/pinecone-io/pinecone-python-client/blob/d9df37558ba35b7097dc4bc5c8101b576019caa2/tests/integration/helpers/helpers.py#L13
If we allow the test runs to create their own names and resources and then clean those up, that feels a bit more robust.
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.
Yes, fantastic idea!
TEST_PODS_INDEX_NAME="<Pod based Index name>" | ||
TEST_SERVERLESS_INDEX_NAME="<Serverless based Index name>" |
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.
buh bai
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.
👋
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.
This makes a lot of sense to me, and I really appreciate the clean up here, and putting us in a better direction in terms of integration test robustness. Nice work! 👏
I do have some questions and follow up around how the IntegrationTests
suite is created and run. I feel like I'm possibly missing something about our integration testing dependencies. We can follow up offline, or in an additional PR to further refine things.
For now though we've retained our coverage and better organized the setup and teardown into a centralized location which I love, and is similar to our approach in Java.
pinecone/client_test_2.go
Outdated
@@ -0,0 +1 @@ | |||
package pinecone |
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.
I think this can be removed, right?
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.
(I know this file is renamed, but answering anyways -- apparently no it needs to be there (says my IDE))
TEST_PODS_INDEX_NAME="<Pod based Index name>" | ||
TEST_SERVERLESS_INDEX_NAME="<Serverless based Index name>" |
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.
👋
@@ -17,8 +17,6 @@ jobs: | |||
run: | | |||
go get ./pinecone | |||
- name: Run tests | |||
run: go test ./pinecone | |||
run: go test -count=1 -v ./pinecone |
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.
I think the default for count
is 1 so you can probably remove explicitly setting it.
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.
I thinkkkk from the documentation it's not, actually! Check this out:
README.md
Outdated
@@ -101,7 +103,7 @@ Then, execute `just bootstrap` to install the necessary Go packages | |||
### .env Setup | |||
|
|||
To avoid race conditions or having to wait for index creation, the tests require a project with at least one pod index |
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.
nit: I think we can remove this whole first part since it's not longer true that we're avoiding waiting for indexes to create.
pinecone/integration_test_suite.go
Outdated
return nil | ||
} | ||
|
||
// TODO: how to get this func to work for client tests too |
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.
I think this TODO can come out, right?
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.
Whoopsie yes :)
pinecone/integration_test_suite.go
Outdated
podIdxName string | ||
serverlessIdxName string |
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.
What's the reason for needing both of these names in each struct? It seems like in run_integration_test_suites.go
, we're creating two different IntegrationTests
objects. Do they not run separately?
I'm just thinking it feels easier to reason about each instance of the struct handling it's own index. I know you mentioned both serverless and pod index tests run regardless of what file you're trying to test, so there might be something I'm misunderstanding. Basically, my initial thoughts were "why can't this just be idxName
and each suite handles one index?
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.
Yeah....I think you're right! Lemme try it out and see if CI still passes...
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
func RunSuites(t *testing.T) { |
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.
I wonder if there's a way to add an additional parameter here like a pod
vs. serverless
enum. We could then directly control what's run when each client_test.go
or index_connection_test.go
runs RunSuites()
, so you could control behavior more directly via integration test file. That feels like it might be nice.
Like you said, there's a lot of ways we could take this and we should probably just take this first step and then play around.
pinecone/index_connection_test.go
Outdated
func TestIndexConnectionIntegration(t *testing.T) { | ||
RunSuites(t) |
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.
So we call RunSuites()
from here in each test file (client_test.go
and index_connection_test.go
). Then in RunSuites()
we're creating two IntegrationTest
structs and then calling suite.Run
on both of them:
suite.Run(t, podTestSuite)
suite.Run(t, serverlessTestSuite)
This may be a testify thing I'm not clear on, but is there a possibility we're running duplicates of the test suites? Like if we trigger go test
on both files, does it spawn different instances of testing.T
and RunSuites()
etc?
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.
Gone! Now we have RunSuites
in suite_runner_test.go
(has to be append with _test
so that go test
runs the RunSuites
func, and test_suite.go
, which is where the setup/teardown lives.
pinecone/integration_test_suite.go
Outdated
if ts.indexType == "serverless" { | ||
indexName = ts.serverlessIdxName | ||
} else if ts.indexType == "pods" { | ||
indexName = ts.podIdxName | ||
} |
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.
I feel like needing to do these indexType
checks inside of this method is harder to reason about, couldn't we just take in an indexName
as an argument and then make this function handle one index no matter what?
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.
gone!
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.
Thanks for addressing all that feedback and getting to the root of the test run stuff! 🚢
Problem
Our current test set up is not as useful as it could be. Some issues are:
Solution
Make it all better!
The current architecture now looks like this:
Two top-level test infra files:
integration_test_suite.go
: This file defines a single teststruct
calledIntegrationTests
that holds the fields for everything we need wrt integration testing across allgo
files in our projecta. Importantly, this file also contains the
testify
mandatorySetupSuite
andTeardownSuite
methods attached to thisstruct
, so that indexes are always torn down after testing completesrun_integration_test_suites.go
: This file actually runs the test suites defined inintegration_test_suite.go
viatestify
'ssuite.Run
command. It runs 2 suites: 1 for pods (podTestSuite
) and 1 for serverless (serverlessTestSuite
)Individual test files:
Each file still has a complementary
..._test.go
file that contains its integration and unit tests. The main difference this PR introduces is that each of these files no longer contains a redundantSetupSuite
function, etc. Instead, they simply callrun_integration_test_suites.go
'sRunSuites()
method, and everything is automagically created/destroyed.Genesis
This refactor arose from Audrey trying to write integration tests for
update
, but being unable to do so, since she could not easily compare IDs, vector values or metadata before vs afterupdate
operations.Misc.:
There is still a lot of things we can do to make our tests better and more efficient, I'm sure. This is just one baby step on the longer journey towards test suite-maturity.
FAQs
I don't like it either, but apparently this is what is needed for
testify
to work 😢 . You can't have thesuite.Run
call in the same file as theSetupSuite
andTeardownSuite
methods.Yes. This obviously isn't ideal for dev work, but you can figure out how to run individual tests via the command line by reading up on
go test
.This is totally fair and tbh I simply didn't refactor this part because this PR is getting gigantic and it seemed like it would add unnecessary complexity to it. But we should go over the pros/cons of having everything in a single
Suite
later! For now, splitting them out produced the invaluable outcome of allowing me to test different things per index type.Type of Change
Test Plan
CI passes.