Skip to content

Commit 010cd3b

Browse files
Add more details to testing bad practices (opensearch-project#14455) (opensearch-project#14473)
These are a few cases I have seen that have resulted in flaky tests. I would love to see more details added here so that this can be used as a sort of checklist when writing, reviewing, or trying to fix tests. (cherry picked from commit e59d77a) Signed-off-by: Andrew Ross <andrross@amazon.com> Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 2a9dd5f commit 010cd3b

File tree

1 file changed

+25
-1
lines changed

1 file changed

+25
-1
lines changed

TESTING.md

+25-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ OpenSearch uses [jUnit](https://junit.org/junit5/) for testing, it also uses ran
3232
- [Bad practices](#bad-practices)
3333
- [Use randomized-testing for coverage](#use-randomized-testing-for-coverage)
3434
- [Abuse randomization in multi-threaded tests](#abuse-randomization-in-multi-threaded-tests)
35+
- [Use `Thread.sleep`](#use-threadsleep)
36+
- [Expect a specific segment topology](#expect-a-specific-segment-topology)
37+
- [Leave environment in an unstable state after test](#leave-environment-in-an-unstable-state-after-test)
3538
- [Test coverage analysis](#test-coverage-analysis)
3639
- [Building with extra plugins](#building-with-extra-plugins)
3740
- [Environment misc](#environment-misc)
@@ -431,7 +434,7 @@ Unit tests are the preferred way to test some functionality: most of the time th
431434

432435
The reason why `OpenSearchSingleNodeTestCase` exists is that all our components used to be very hard to set up in isolation, which had led us to having a number of integration tests but close to no unit tests. `OpenSearchSingleNodeTestCase` is a workaround for this issue which provides an easy way to spin up a node and get access to components that are hard to instantiate like `IndicesService`. Whenever practical, you should prefer unit tests.
433436

434-
Finally, if the the functionality under test needs to be run in a cluster, there are two test classes to consider:
437+
Finally, if the functionality under test needs to be run in a cluster, there are two test classes to consider:
435438
* `OpenSearchRestTestCase` will connect to an external cluster. This is a good option if the tests cases don't rely on a specific configuration of the test cluster. A test cluster is set up as part of the Gradle task running integration tests, and test cases using this class can connect to it. The configuration of the cluster is provided in the Gradle files.
436439
* `OpenSearchIntegTestCase` will create a local cluster as part of each test case. The configuration of the cluster is controlled by the test class. This is a good option if different tests cases depend on different cluster configurations, as it would be impractical (and limit parallelization) to keep re-configuring (and re-starting) the external cluster for each test case. A good example of when this class might come in handy is for testing security features, where different cluster configurations are needed to fully test each one.
437440

@@ -453,6 +456,27 @@ However, it should not be used for coverage. For instance if you are testing a p
453456

454457
Multi-threaded tests are often not reproducible due to the fact that there is no guarantee on the order in which operations occur across threads. Adding randomization to the mix usually makes things worse and should be done with care.
455458

459+
### Use `Thread.sleep`
460+
461+
`Thread.sleep()` is almost always a bad idea because it is very difficult to know that you've waited long enough. Using primitives like `waitUntil` or `assertBusy`, which use Thread.sleep internally, is okay to wait for a specific condition. However, it is almost always better to instrument your code with concurrency primitives like a `CountDownLatch` that will allow you to deterministically wait for a specific condition, without waiting longer than necessary that will happen with a polling approach used by `assertBusy`.
462+
463+
Example:
464+
- [PrimaryShardAllocatorIT](https://github.com/opensearch-project/OpenSearch/blob/7ffcd6500e0bd5956cef5c289ee66d9f99d533fc/server/src/internalClusterTest/java/org/opensearch/gateway/ReplicaShardAllocatorIT.java#L208-L235): This test is using two latches: one to wait for a recovery to start and one to block that recovery so that it can deterministically test things that happen during a recovery.
465+
466+
### Expect a specific segment topology
467+
468+
By design, OpenSearch integration tests will vary how the merge policy works because in almost all scenarios you should not depend on a specific segment topology (in the real world your code will see a huge diversity of indexing workloads with OpenSearch merging things in the background all the time!). If you do in fact need to care about the segment topology (e.g. for testing statistics that might vary slightly depending on number of segments), then you must take care to ensure that segment topology is deterministic by doing things like disabling background refreshes, force merging after indexing data, etc.
469+
470+
Example:
471+
- [SegmentReplicationResizeRequestIT](https://github.com/opensearch-project/OpenSearch/blob/f715ee1a485e550802accc1c2e3d8101208d4f0b/server/src/internalClusterTest/java/org/opensearch/indices/replication/SegmentReplicationResizeRequestIT.java#L102-L109): This test disables refreshes to prevent interfering with the segment replication behavior under test.
472+
473+
### Leave environment in an unstable state after test
474+
475+
The default test case will ensure that no open file handles or running threads are left after tear down. You must ensure that all resources are cleaned up at the end of each test case, or else the cleanup may end up racing with the tear down logic in the base test class in a way that is very difficult to reproduce.
476+
477+
Example:
478+
- [AwarenessAttributeDecommissionIT](https://github.com/opensearch-project/OpenSearch/blob/main/server/src/internalClusterTest/java/org/opensearch/cluster/coordination/AwarenessAttributeDecommissionIT.java#L951): Recommissions any decommissioned nodes at the end of the test to ensure the after-test checks succeed.
479+
456480
# Test coverage analysis
457481

458482
The code coverage report can be generated through Gradle with [JaCoCo plugin](https://docs.gradle.org/current/userguide/jacoco_plugin.html).

0 commit comments

Comments
 (0)