Skip to content
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

Improve performance of the Go test suite #2060

Merged
merged 18 commits into from
Sep 20, 2021
Merged

Conversation

mna
Copy link
Member

@mna mna commented Sep 14, 2021

Closes #1805 .

This PR addresses two categories of slow tests: network requests and creating temporary mysql databases.

Network Requests

I introduced the NETWORK_TEST=1 environment variable, similar to how we have MYSQL_TEST and REDIS_TEST to enable those specific tests. I did enable them on CI as we do want to run them, but it is easier to skip when running tests locally (i.e. make test-go does not run them automatically).

It's not the ideal solution, I would've liked to mock them completely by starting a test server and mocking responses, but after a closer look this seems quite complex to setup as the data returned has a specific format (e.g. for vulnerabilities) and hashes that must match and the requests themselves are sometimes made quite deep in the code (even in 3rd-party code).

Temporary MySQL Databases

I refactored the tests in server/datastore/mysql to use sub-tests and drop the number of temporary databases created from 130 to 24. I opted to keep a top-level test (that creates the temp DB) for each "entity" (or .go file) to make it easier to identify the cleanup required and so that the test setup applies to the tests in the same file, a bit easier to reason about and to read the tests that way, I think.

The speedup is quite interesting too, comparing MYSQL_TEST=1 go test ./server/datastore/mysql/ -count=1 locally on this branch vs main, I get ~10s vs ~30s (though part of it is also in reducing the number of records created in "load" tests to verify mysql locking).

Also, the race detector (MYSQL_TEST=1 go test ./server/datastore/mysql/ -count=1 -race) now passes, there was a data race previously (in the test code, so nothing critical).

Checklist for submitter

If some of the following don't apply, please write a short explanation why.

  • Changes file added (if needed)
  • Documented any API changes
  • Added tests for all functionality
  • Manual QA for all functionality

@codecov-commenter
Copy link

codecov-commenter commented Sep 14, 2021

Codecov Report

Merging #2060 (859e3e4) into main (1fa5ce1) will increase coverage by 0.11%.
The diff coverage is 75.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2060      +/-   ##
==========================================
+ Coverage   48.03%   48.15%   +0.11%     
==========================================
  Files         224      224              
  Lines       21266    21271       +5     
==========================================
+ Hits        10216    10243      +27     
+ Misses       9834     9809      -25     
- Partials     1216     1219       +3     
Impacted Files Coverage Δ
server/datastore/mysql/software.go 74.44% <75.00%> (+0.48%) ⬆️
server/sso/session_store.go 59.45% <0.00%> (+59.45%) ⬆️

Continue to review full report at Codecov.

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

@mna mna force-pushed the mna-1805-faster-tests branch 2 times, most recently from 1b81577 to e44b2b1 Compare September 20, 2021 14:42
cmd/fleetctl/convert_test.go Show resolved Hide resolved
@@ -10,10 +10,25 @@ import (
"github.com/stretchr/testify/require"
)

func TestActivityUsernameChange(t *testing.T) {
func TestActivity(t *testing.T) {
Copy link
Member Author

Choose a reason for hiding this comment

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

@chiiph as discussed in #1821 , this is how I see "test suites" being implemented in standard Go tests/sub-tests:

  • the "suite" is the top-level test function (for mysql I created one by test file, with the name being the name of the file/entity).
  • the suite setup/teardown is any code executed and deferred (respectively) before the sub-tests (can also be t.Cleanup instead of defer, as is the case in CreateMySQLDS).
  • I used a table-driven approach to run the suite's tests, as it tends to minimize code repetition.
  • Per-test setup/teardown is just the code inside the t.Run closure that runs before calling the test function.
  • If some test has specific setup/teardown, of course it can simply be done in that test's function.

In this case I did not need any shared state so I didn't use a struct to hold the suite's tests, but it is a possibility (I could've used a struct and store e.g. the datastore there so its methods would've accessed it there instead of as an argument).

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, makes sense and it's simple enough.

server/datastore/mysql/software_test.go Show resolved Hide resolved
server/datastore/mysql/software.go Show resolved Hide resolved
server/datastore/mysql/sessions_test.go Show resolved Hide resolved
server/datastore/mysql/activities_test.go Show resolved Hide resolved
@@ -1584,7 +1558,9 @@ func TestSaveTonsOfUsers(t *testing.T) {
errCh <- err
return
}
atomic.AddInt32(&count1, 1)
if atomic.AddInt32(&count1, 1) >= 100 {
Copy link
Member Author

Choose a reason for hiding this comment

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

@chiiph let me know if this is still good enough for the goal of the test (which I believe was related to locking when adding many users concurrently). I tried to find a balance between the batch size and the test duration.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's probably fine, the locking happened pretty much right away.

server/datastore/mysql/hosts_test.go Show resolved Hide resolved
@mna mna marked this pull request as ready for review September 20, 2021 15:38
@mna mna changed the title [wip] Improve performance of the Go test suite Improve performance of the Go test suite Sep 20, 2021
Copy link
Contributor

@chiiph chiiph left a comment

Choose a reason for hiding this comment

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

Looks great! Left a few minor comments.

@@ -41,7 +41,7 @@ jobs:

- name: Run Go Tests
run: |
MYSQL_TEST=1 make test-go
NETWORK_TEST=1 REDIS_TEST=1 MYSQL_TEST=1 make test-go
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a huge fan of these, but I get it.

We did remove REDIS_TEST, so we should remove it from here. I would also like to remove MYSQL_TEST entirely and just keep working on making things better.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I agree with you, it's not very discoverable, but as mentioned in the description the approach I would've preferred was quite an undertaking, especially without fully understanding that part of the code. I believe the REDIS_TEST env var is still used in the server/sso tests.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh... darn, missed that :( Allllrighty then

@@ -10,10 +10,25 @@ import (
"github.com/stretchr/testify/require"
)

func TestActivityUsernameChange(t *testing.T) {
func TestActivity(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, makes sense and it's simple enough.

@@ -1584,7 +1558,9 @@ func TestSaveTonsOfUsers(t *testing.T) {
errCh <- err
return
}
atomic.AddInt32(&count1, 1)
if atomic.AddInt32(&count1, 1) >= 100 {
Copy link
Contributor

Choose a reason for hiding this comment

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

It's probably fine, the locking happened pretty much right away.

}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
defer TruncateTables(t, ds, "teams", "invites", "invite_teams")
Copy link
Contributor

Choose a reason for hiding this comment

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

Would truncating all the tables be much slower?

Copy link
Member Author

Choose a reason for hiding this comment

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

Probably not, though some are seeded by the sql schema file and should be left alone. It's usually quite clear when one is missing (as a subsequent test will fail with a duplicate error or wrong number of elements, etc.), but yeah I don't think it'd be significantly slower. I'll try it out.

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated the PR, it seems roughly as fast (10-12 seconds on my machine).

@@ -233,7 +233,9 @@ func createMySQLDSWithOptions(t *testing.T, opts *DatastoreTestOptions) *Datasto
strings.TrimPrefix(details.Name(), "github.com/fleetdm/fleet/v4/"), "/", "_",
)
cleanName = strings.ReplaceAll(cleanName, ".", "_")
return initializeDatabase(t, cleanName, opts)
ds := initializeDatabase(t, cleanName, opts)
t.Cleanup(func() { ds.Close() })
Copy link
Contributor

Choose a reason for hiding this comment

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

💪

@mna mna merged commit 86dce78 into fleetdm:main Sep 20, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Minimize number of temp mysql databases created in tests
3 participants