-
Notifications
You must be signed in to change notification settings - Fork 134
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
RUMM-1890 Fix tests flakiness #711
Changes from all commits
062f279
583766c
b97b9ea
ab4d65d
cc5058d
36d0b47
70d8729
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -151,7 +151,7 @@ class RUMApplicationScopeTests: XCTestCase { | |
backgroundEventTrackingEnabled: .mockAny() | ||
) | ||
|
||
let simulatedSessionsCount = 200 | ||
let simulatedSessionsCount = 400 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using tests repetition in local:
With 400 samples it runs in |
||
(0..<simulatedSessionsCount).forEach { _ in | ||
_ = scope.process(command: RUMStartViewCommand.mockWith(time: currentTime, identity: mockView)) | ||
_ = scope.process(command: RUMStopViewCommand.mockWith(time: currentTime, identity: mockView)) | ||
|
@@ -161,7 +161,8 @@ class RUMApplicationScopeTests: XCTestCase { | |
let viewEventsCount = try output.recordedEvents(ofType: RUMEvent<RUMViewEvent>.self).count | ||
let trackedSessionsCount = Double(viewEventsCount) / 2 // each Session should send 2 View updates | ||
|
||
XCTAssertGreaterThan(trackedSessionsCount, 100 * 0.8) // -20% | ||
XCTAssertLessThan(trackedSessionsCount, 100 * 1.2) // +20% | ||
let halfSessionsCount = 0.5 * Double(simulatedSessionsCount) | ||
XCTAssertGreaterThan(trackedSessionsCount, halfSessionsCount * 0.8) // -20% | ||
XCTAssertLessThan(trackedSessionsCount, halfSessionsCount * 1.2) // +20% | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -89,12 +89,15 @@ class RUMViewIdentityTests: XCTestCase { | |
// MARK: - Memory management | ||
|
||
func testItStoresWeakReferenceToUIViewController() throws { | ||
var vc: UIViewController? = UIViewController() | ||
var identity: RUMViewIdentity! // swiftlint:disable:this implicitly_unwrapped_optional | ||
|
||
let identity = try XCTUnwrap(vc?.asRUMViewIdentity()) | ||
try autoreleasepool { | ||
var vc: UIViewController? = UIViewController() | ||
identity = try XCTUnwrap(vc?.asRUMViewIdentity()) | ||
XCTAssertNotNil(identity.identifiable, "Reference should be available while `vc` is alive.") | ||
vc = nil | ||
} | ||
|
||
XCTAssertNotNil(identity.identifiable, "Reference should be available while `vc` is alive.") | ||
vc = nil | ||
XCTAssertNil(identity.identifiable, "Reference should not be available after `vc` was deallocated.") | ||
Comment on lines
-94
to
101
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2 failures in last 1 month. It was failing on: XCTAssertNotNil(identity.identifiable, "Reference should be available while `vc` is alive.") I assume that |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,43 +11,54 @@ import UIKit | |
class VitalRefreshRateReaderTests: XCTestCase { | ||
private let mockNotificationCenter = NotificationCenter() | ||
|
||
func testHighAndLowRefreshRates() { | ||
func testWhenMainThreadOverheadGoesUp_itMeasuresLowerRefreshRate() throws { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3 failures in last 1 month. This is quite old issue, I changed the approach for this test:
It passes 500x repetition in local. From my observation, time-based constraint was very flaky (sometimes recording just 1 sample, sometimes many). Current approach of recording 30 samples seems stable and still executes under |
||
let reader = VitalRefreshRateReader(notificationCenter: mockNotificationCenter) | ||
let registrar_view1 = VitalPublisher(initialValue: VitalInfo()) | ||
let registrar_view2 = VitalPublisher(initialValue: VitalInfo()) | ||
|
||
// View1 has simple UI, high FPS expected | ||
reader.register(registrar_view1) | ||
|
||
// Wait without blocking UI thread | ||
let expectation1 = expectation(description: "async expectation for first observer") | ||
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { | ||
expectation1.fulfill() | ||
let targetSamplesCount = 30 | ||
|
||
/// Runs given work on the main thread until `condition` is met, then calls `completion`. | ||
func run(mainThreadWork: @escaping () -> Void, until condition: @escaping () -> Bool, completion: @escaping () -> Void) { | ||
if !condition() { | ||
mainThreadWork() | ||
DispatchQueue.main.async { // schedule to next runloop | ||
run(mainThreadWork: mainThreadWork, until: condition, completion: completion) | ||
} | ||
} else { | ||
completion() | ||
} | ||
} | ||
|
||
waitForExpectations(timeout: 1.0) { _ in | ||
XCTAssertGreaterThan(registrar_view1.currentValue.sampleCount, 0) | ||
XCTAssertGreaterThan(UIScreen.main.maximumFramesPerSecond, Int(registrar_view1.currentValue.maxValue!)) | ||
XCTAssertGreaterThan(registrar_view1.currentValue.minValue!, 0.0) | ||
/// Records `targetSamplesCount` samples into `measure` by running given work on the main thread. | ||
func record(_ measure: VitalPublisher, mainThreadWork: @escaping () -> Void) { | ||
let completion = expectation(description: "Complete measurement") | ||
reader.register(measure) | ||
|
||
run( | ||
mainThreadWork: mainThreadWork, | ||
until: { measure.currentValue.sampleCount == targetSamplesCount }, | ||
completion: { | ||
reader.unregister(measure) | ||
completion.fulfill() | ||
} | ||
) | ||
|
||
wait(for: [completion], timeout: 10) | ||
} | ||
|
||
reader.unregister(registrar_view1) | ||
// Given | ||
let lowOverhead = { /* no-op */ } // no overhead in succeeding runloop runs | ||
let lowOverheadMeasure = VitalPublisher(initialValue: VitalInfo()) | ||
|
||
// View2 has complex UI, lower FPS expected | ||
reader.register(registrar_view2) | ||
let highOverhead = { Thread.sleep(forTimeInterval: 0.02) } // 0.02 overhead in succeeding runloop runs | ||
let highOverheadMeasure = VitalPublisher(initialValue: VitalInfo()) | ||
|
||
// Block UI thread | ||
Thread.sleep(forTimeInterval: 1.0) | ||
|
||
// Wait after blocking UI thread so that reader will read refresh rate before assertions | ||
let expectation2 = expectation(description: "async expectation for second observer") | ||
DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) { | ||
expectation2.fulfill() | ||
} | ||
waitForExpectations(timeout: 0.5) { _ in } | ||
// When | ||
record(lowOverheadMeasure, mainThreadWork: lowOverhead) | ||
record(highOverheadMeasure, mainThreadWork: highOverhead) | ||
|
||
XCTAssertGreaterThan(registrar_view2.currentValue.sampleCount, 0) | ||
XCTAssertGreaterThan(registrar_view1.currentValue.meanValue!, registrar_view2.currentValue.meanValue!) | ||
// Then | ||
let expectedHighFPS = try XCTUnwrap(lowOverheadMeasure.currentValue.meanValue) | ||
let expectedLowFPS = try XCTUnwrap(highOverheadMeasure.currentValue.meanValue) | ||
XCTAssertGreaterThan(expectedHighFPS, expectedLowFPS, "It must measure higher FPS for lower main thread overhead") | ||
} | ||
|
||
func testAppStateHandling() { | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -146,12 +146,23 @@ extension XCTestCase { | |||||||||||||||||||||||||||||||||||||||||||||
) throws { | ||||||||||||||||||||||||||||||||||||||||||||||
let prettyEncoder = JSONEncoder() | ||||||||||||||||||||||||||||||||||||||||||||||
prettyEncoder.outputFormatting = [.prettyPrinted, .sortedKeys] | ||||||||||||||||||||||||||||||||||||||||||||||
let encodedValue1 = try prettyEncoder.encode(value1) | ||||||||||||||||||||||||||||||||||||||||||||||
let encodedValue2 = try prettyEncoder.encode(value2) | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
let value1JSONString = encodedValue1.utf8String | ||||||||||||||||||||||||||||||||||||||||||||||
let value2JSONString = encodedValue2.utf8String | ||||||||||||||||||||||||||||||||||||||||||||||
XCTAssertEqual(value1JSONString, value2JSONString, file: file, line: line) | ||||||||||||||||||||||||||||||||||||||||||||||
do { | ||||||||||||||||||||||||||||||||||||||||||||||
let encodedValue1: Data | ||||||||||||||||||||||||||||||||||||||||||||||
let encodedValue2: Data | ||||||||||||||||||||||||||||||||||||||||||||||
if #available(iOS 13.0, *) { | ||||||||||||||||||||||||||||||||||||||||||||||
encodedValue1 = try prettyEncoder.encode(value1) | ||||||||||||||||||||||||||||||||||||||||||||||
encodedValue2 = try prettyEncoder.encode(value2) | ||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||
encodedValue1 = try prettyEncoder.encode(EncodingContainer(value1)) | ||||||||||||||||||||||||||||||||||||||||||||||
encodedValue2 = try prettyEncoder.encode(EncodingContainer(value2)) | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+152
to
+158
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This fixes the biggest problem in last 1 month - 14 failures in: dd-sdk-ios/Tests/DatadogTests/Datadog/CrashReporting/CrashContext/CrashContextTests.swift Lines 49 to 65 in e3468ed
When fuzzy input is resolved to dd-sdk-ios/Tests/DatadogTests/Helpers/Encoding.swift Lines 9 to 13 in e3468ed
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👌 |
||||||||||||||||||||||||||||||||||||||||||||||
let value1JSONString = encodedValue1.utf8String | ||||||||||||||||||||||||||||||||||||||||||||||
let value2JSONString = encodedValue2.utf8String | ||||||||||||||||||||||||||||||||||||||||||||||
XCTAssertEqual(value1JSONString, value2JSONString, file: file, line: line) | ||||||||||||||||||||||||||||||||||||||||||||||
} catch let exception { | ||||||||||||||||||||||||||||||||||||||||||||||
XCTFail("Failed to encode one of the values in `AssertEncodedRepresentationsEqual`", file: file, line: line) | ||||||||||||||||||||||||||||||||||||||||||||||
throw exception | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
func AssertURLSessionTasksIdentical( | ||||||||||||||||||||||||||||||||||||||||||||||
|
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.
It failed 2 times over 1 month, but no clue on what's happening wrong in this test. I'm just adding more verbosity to these assertions, so we can better understand it.