2024-11-19 11:51:32 +05:30
|
|
|
// Tests ported from ZAP Java version of the algorithm
|
|
|
|
|
|
|
|
|
|
package time
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"math/rand"
|
2025-02-13 18:46:28 +05:30
|
|
|
"reflect"
|
2024-11-19 11:51:32 +05:30
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
// This test suite verifies the timing dependency detection algorithm by testing various scenarios:
|
|
|
|
|
//
|
|
|
|
|
// Test Categories:
|
|
|
|
|
// 1. Perfect Linear Cases
|
|
|
|
|
// - TestPerfectLinear: Basic case with slope=1, no noise
|
|
|
|
|
// - TestPerfectLinearSlopeOne_NoNoise: Similar to above but with different parameters
|
|
|
|
|
// - TestPerfectLinearSlopeTwo_NoNoise: Tests detection of slope=2 relationship
|
|
|
|
|
//
|
|
|
|
|
// 2. Noisy Cases
|
|
|
|
|
// - TestLinearWithNoise: Verifies detection works with moderate noise (±0.2s)
|
|
|
|
|
// - TestNoisyLinear: Similar but with different noise parameters
|
|
|
|
|
// - TestHighNoiseConcealsSlope: Verifies detection fails with extreme noise (±5s)
|
|
|
|
|
//
|
|
|
|
|
// 3. No Correlation Cases
|
|
|
|
|
// - TestNoCorrelation: Basic case where delay has no effect
|
|
|
|
|
// - TestNoCorrelationHighBaseline: High baseline (~15s) masks any delay effect
|
|
|
|
|
// - TestNegativeSlopeScenario: Verifies detection rejects negative correlations
|
|
|
|
|
//
|
|
|
|
|
// 4. Edge Cases
|
|
|
|
|
// - TestMinimalData: Tests behavior with minimal data points (2 requests)
|
|
|
|
|
// - TestLargeNumberOfRequests: Tests stability with many data points (20 requests)
|
|
|
|
|
// - TestChangingBaseline: Tests detection with shifting baseline mid-test
|
|
|
|
|
// - TestHighBaselineLowSlope: Tests detection of subtle correlations (slope=0.85)
|
|
|
|
|
//
|
|
|
|
|
// ZAP Test Cases:
|
|
|
|
|
//
|
|
|
|
|
// 1. Alternating Sequence Tests
|
|
|
|
|
// - TestAlternatingSequences: Verifies correct alternation between high and low delays
|
|
|
|
|
//
|
|
|
|
|
// 2. Non-Injectable Cases
|
|
|
|
|
// - TestNonInjectableQuickFail: Tests quick failure when response time < requested delay
|
|
|
|
|
// - TestSlowNonInjectableCase: Tests early termination with consistently high response times
|
|
|
|
|
// - TestRealWorldNonInjectableCase: Tests behavior with real-world response patterns
|
|
|
|
|
//
|
|
|
|
|
// 3. Error Tolerance Tests
|
|
|
|
|
// - TestSmallErrorDependence: Verifies detection works with small random variations
|
|
|
|
|
//
|
|
|
|
|
// Key Parameters Tested:
|
|
|
|
|
// - requestsLimit: Number of requests to make (2-20)
|
|
|
|
|
// - highSleepTimeSeconds: Maximum delay to test (typically 5s)
|
|
|
|
|
// - correlationErrorRange: Acceptable deviation from perfect correlation (0.05-0.3)
|
|
|
|
|
// - slopeErrorRange: Acceptable deviation from expected slope (0.1-1.5)
|
|
|
|
|
//
|
|
|
|
|
// The test suite uses various mock senders (perfectLinearSender, noCorrelationSender, etc.)
|
|
|
|
|
// to simulate different timing behaviors and verify the detection algorithm works correctly
|
|
|
|
|
// across a wide range of scenarios.
|
2024-11-19 11:51:32 +05:30
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
// Mock request sender that simulates a perfect linear relationship:
|
|
|
|
|
// Observed delay = baseline + requested_delay
|
|
|
|
|
func perfectLinearSender(baseline float64) func(delay int) (float64, error) {
|
|
|
|
|
return func(delay int) (float64, error) {
|
|
|
|
|
// simulate some processing time
|
|
|
|
|
time.Sleep(10 * time.Millisecond) // just a small artificial sleep to mimic network
|
|
|
|
|
return baseline + float64(delay), nil
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-19 11:51:32 +05:30
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
// Mock request sender that simulates no correlation:
|
|
|
|
|
// The response time is random around a certain constant baseline, ignoring requested delay.
|
|
|
|
|
func noCorrelationSender(baseline, noiseAmplitude float64) func(int) (float64, error) {
|
|
|
|
|
return func(delay int) (float64, error) {
|
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
|
noise := 0.0
|
|
|
|
|
if noiseAmplitude > 0 {
|
|
|
|
|
noise = (rand.Float64()*2 - 1) * noiseAmplitude
|
|
|
|
|
}
|
|
|
|
|
return baseline + noise, nil
|
2024-11-19 11:51:32 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
// Mock request sender that simulates partial linearity but with some noise.
|
|
|
|
|
func noisyLinearSender(baseline float64) func(delay int) (float64, error) {
|
|
|
|
|
return func(delay int) (float64, error) {
|
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
|
// Add some noise (±0.2s) to a linear relationship
|
|
|
|
|
noise := 0.2
|
|
|
|
|
return baseline + float64(delay) + noise, nil
|
2024-11-19 11:51:32 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
func TestPerfectLinear(t *testing.T) {
|
|
|
|
|
// Expect near-perfect correlation and slope ~ 1.0
|
|
|
|
|
requestsLimit := 6 // 3 pairs: enough data for stable regression
|
|
|
|
|
highSleepTimeSeconds := 5
|
|
|
|
|
corrErrRange := 0.1
|
|
|
|
|
slopeErrRange := 0.2
|
|
|
|
|
baseline := 5.0
|
|
|
|
|
|
|
|
|
|
sender := perfectLinearSender(5.0) // baseline 5s, observed = 5s + requested_delay
|
|
|
|
|
match, reason, err := checkTimingDependency(
|
|
|
|
|
requestsLimit,
|
|
|
|
|
highSleepTimeSeconds,
|
|
|
|
|
corrErrRange,
|
|
|
|
|
slopeErrRange,
|
|
|
|
|
baseline,
|
|
|
|
|
sender,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if !match {
|
|
|
|
|
t.Fatalf("Expected a match but got none. Reason: %s", reason)
|
2024-11-19 11:51:32 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
func TestNoCorrelation(t *testing.T) {
|
|
|
|
|
// Expect no match because requested delay doesn't influence observed delay
|
|
|
|
|
requestsLimit := 6
|
|
|
|
|
highSleepTimeSeconds := 5
|
|
|
|
|
corrErrRange := 0.1
|
|
|
|
|
slopeErrRange := 0.5
|
|
|
|
|
baseline := 8.0
|
|
|
|
|
|
|
|
|
|
sender := noCorrelationSender(8.0, 0.1)
|
|
|
|
|
match, reason, err := checkTimingDependency(
|
|
|
|
|
requestsLimit,
|
|
|
|
|
highSleepTimeSeconds,
|
|
|
|
|
corrErrRange,
|
|
|
|
|
slopeErrRange,
|
|
|
|
|
baseline,
|
|
|
|
|
sender,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if match {
|
|
|
|
|
t.Fatalf("Expected no match but got one. Reason: %s", reason)
|
2024-11-19 11:51:32 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
func TestNoisyLinear(t *testing.T) {
|
|
|
|
|
// Even with some noise, it should detect a strong positive correlation if
|
|
|
|
|
// we allow a slightly bigger margin for slope/correlation.
|
|
|
|
|
requestsLimit := 10 // More requests to average out noise
|
|
|
|
|
highSleepTimeSeconds := 5
|
|
|
|
|
corrErrRange := 0.2 // allow some lower correlation due to noise
|
|
|
|
|
slopeErrRange := 0.5 // slope may deviate slightly
|
|
|
|
|
baseline := 2.0
|
|
|
|
|
|
|
|
|
|
sender := noisyLinearSender(2.0) // baseline 2s, observed ~ 2s + requested_delay ±0.2
|
|
|
|
|
match, reason, err := checkTimingDependency(
|
|
|
|
|
requestsLimit,
|
|
|
|
|
highSleepTimeSeconds,
|
|
|
|
|
corrErrRange,
|
|
|
|
|
slopeErrRange,
|
|
|
|
|
baseline,
|
|
|
|
|
sender,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We expect a match since it's still roughly linear. The slope should be close to 1.
|
|
|
|
|
if !match {
|
|
|
|
|
t.Fatalf("Expected a match in noisy linear test but got none. Reason: %s", reason)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestMinimalData(t *testing.T) {
|
|
|
|
|
// With too few requests, correlation might not be stable.
|
|
|
|
|
// Here, we send only 2 requests (1 pair) and see if the logic handles it gracefully.
|
|
|
|
|
requestsLimit := 2
|
|
|
|
|
highSleepTimeSeconds := 5
|
|
|
|
|
corrErrRange := 0.3
|
|
|
|
|
slopeErrRange := 0.5
|
|
|
|
|
baseline := 5.0
|
|
|
|
|
|
|
|
|
|
// Perfect linear sender again
|
|
|
|
|
sender := perfectLinearSender(5.0)
|
|
|
|
|
match, reason, err := checkTimingDependency(
|
|
|
|
|
requestsLimit,
|
|
|
|
|
highSleepTimeSeconds,
|
|
|
|
|
corrErrRange,
|
|
|
|
|
slopeErrRange,
|
|
|
|
|
baseline,
|
|
|
|
|
sender,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if !match {
|
|
|
|
|
t.Fatalf("Expected match but got none. Reason: %s", reason)
|
2024-11-19 11:51:32 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
// Utility functions to generate different behaviors
|
|
|
|
|
|
|
|
|
|
// linearSender returns a sender that calculates observed delay as:
|
|
|
|
|
// observed = baseline + slope * requested_delay + noise
|
|
|
|
|
func linearSender(baseline, slope, noiseAmplitude float64) func(int) (float64, error) {
|
|
|
|
|
return func(delay int) (float64, error) {
|
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
|
noise := 0.0
|
|
|
|
|
if noiseAmplitude > 0 {
|
|
|
|
|
noise = (rand.Float64()*2 - 1) * noiseAmplitude // random noise in [-noiseAmplitude, noiseAmplitude]
|
|
|
|
|
}
|
|
|
|
|
return baseline + slope*float64(delay) + noise, nil
|
2024-11-19 11:51:32 +05:30
|
|
|
}
|
2025-02-13 18:46:28 +05:30
|
|
|
}
|
2024-11-19 11:51:32 +05:30
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
// negativeSlopeSender just for completeness - higher delay = less observed time
|
|
|
|
|
func negativeSlopeSender(baseline float64) func(int) (float64, error) {
|
|
|
|
|
return func(delay int) (float64, error) {
|
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
|
return baseline - float64(delay)*2.0, nil
|
2024-11-19 11:51:32 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
func TestPerfectLinearSlopeOne_NoNoise(t *testing.T) {
|
|
|
|
|
baseline := 2.0
|
|
|
|
|
match, reason, err := checkTimingDependency(
|
|
|
|
|
10, // requestsLimit
|
|
|
|
|
5, // highSleepTimeSeconds
|
|
|
|
|
0.1, // correlationErrorRange
|
|
|
|
|
0.2, // slopeErrorRange (allowing slope between 0.8 and 1.2)
|
|
|
|
|
baseline,
|
|
|
|
|
linearSender(baseline, 1.0, 0.0),
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Unexpected error: %v", err)
|
2024-11-19 11:51:32 +05:30
|
|
|
}
|
2025-02-13 18:46:28 +05:30
|
|
|
if !match {
|
|
|
|
|
t.Fatalf("Expected a match for perfect linear slope=1. Reason: %s", reason)
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-19 11:51:32 +05:30
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
func TestPerfectLinearSlopeTwo_NoNoise(t *testing.T) {
|
|
|
|
|
baseline := 2.0
|
|
|
|
|
// slope=2 means observed = baseline + 2*requested_delay
|
|
|
|
|
match, reason, err := checkTimingDependency(
|
|
|
|
|
10,
|
|
|
|
|
5,
|
|
|
|
|
0.1, // correlation must still be good
|
|
|
|
|
1.5, // allow slope in range (0.5 to 2.5), we should be close to 2.0 anyway
|
|
|
|
|
baseline,
|
|
|
|
|
linearSender(baseline, 2.0, 0.0),
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if !match {
|
|
|
|
|
t.Fatalf("Expected a match for slope=2. Reason: %s", reason)
|
2024-11-19 11:51:32 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
func TestLinearWithNoise(t *testing.T) {
|
|
|
|
|
baseline := 5.0
|
|
|
|
|
// slope=1 but with noise ±0.2 seconds
|
|
|
|
|
match, reason, err := checkTimingDependency(
|
|
|
|
|
12,
|
|
|
|
|
5,
|
|
|
|
|
0.2, // correlationErrorRange relaxed to account for noise
|
|
|
|
|
0.5, // slopeErrorRange also relaxed
|
|
|
|
|
baseline,
|
|
|
|
|
linearSender(baseline, 1.0, 0.2),
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Error: %v", err)
|
2024-11-19 11:51:32 +05:30
|
|
|
}
|
2025-02-13 18:46:28 +05:30
|
|
|
if !match {
|
|
|
|
|
t.Fatalf("Expected a match for noisy linear data. Reason: %s", reason)
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-19 11:51:32 +05:30
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
func TestNoCorrelationHighBaseline(t *testing.T) {
|
|
|
|
|
baseline := 15.0
|
|
|
|
|
// baseline ~15s, requested delays won't matter
|
|
|
|
|
match, reason, err := checkTimingDependency(
|
|
|
|
|
10,
|
|
|
|
|
5,
|
|
|
|
|
0.1, // correlation should be near zero, so no match expected
|
|
|
|
|
0.5,
|
|
|
|
|
baseline,
|
|
|
|
|
noCorrelationSender(baseline, 0.1),
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if match {
|
|
|
|
|
t.Fatalf("Expected no match for no correlation scenario. Got: %s", reason)
|
2024-11-19 11:51:32 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
func TestNegativeSlopeScenario(t *testing.T) {
|
|
|
|
|
baseline := 10.0
|
|
|
|
|
// Increasing delay decreases observed time
|
|
|
|
|
match, reason, err := checkTimingDependency(
|
|
|
|
|
10,
|
|
|
|
|
5,
|
|
|
|
|
0.2,
|
|
|
|
|
0.5,
|
|
|
|
|
baseline,
|
|
|
|
|
negativeSlopeSender(baseline),
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Error: %v", err)
|
2024-11-19 11:51:32 +05:30
|
|
|
}
|
2025-02-13 18:46:28 +05:30
|
|
|
if match {
|
|
|
|
|
t.Fatalf("Expected no match in negative slope scenario. Reason: %s", reason)
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-19 11:51:32 +05:30
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
func TestLargeNumberOfRequests(t *testing.T) {
|
|
|
|
|
baseline := 1.0
|
|
|
|
|
// 20 requests, slope=1.0, no noise. Should be very stable and produce a very high correlation.
|
|
|
|
|
match, reason, err := checkTimingDependency(
|
|
|
|
|
20,
|
|
|
|
|
5,
|
|
|
|
|
0.05, // very strict correlation requirement
|
|
|
|
|
0.1, // very strict slope range
|
|
|
|
|
baseline,
|
|
|
|
|
linearSender(baseline, 1.0, 0.0),
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if !match {
|
|
|
|
|
t.Fatalf("Expected a strong match with many requests and perfect linearity. Reason: %s", reason)
|
2024-11-19 11:51:32 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
func TestHighBaselineLowSlope(t *testing.T) {
|
|
|
|
|
baseline := 15.0
|
|
|
|
|
match, reason, err := checkTimingDependency(
|
|
|
|
|
10,
|
|
|
|
|
5,
|
|
|
|
|
0.2,
|
|
|
|
|
0.2, // expecting slope around 0.5, allow range ~0.4 to 0.6
|
|
|
|
|
baseline,
|
|
|
|
|
linearSender(baseline, 0.85, 0.0),
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if !match {
|
|
|
|
|
t.Fatalf("Expected a match for slope=0.5 linear scenario. Reason: %s", reason)
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-19 11:51:32 +05:30
|
|
|
|
2025-02-13 18:46:28 +05:30
|
|
|
func TestHighNoiseConcealsSlope(t *testing.T) {
|
|
|
|
|
baseline := 5.0
|
|
|
|
|
// slope=1, but noise=5 seconds is huge and might conceal the correlation.
|
|
|
|
|
// With large noise, the test may fail to detect correlation.
|
|
|
|
|
match, reason, err := checkTimingDependency(
|
|
|
|
|
12,
|
|
|
|
|
5,
|
|
|
|
|
0.1, // still strict
|
|
|
|
|
0.2, // still strict
|
|
|
|
|
baseline,
|
|
|
|
|
linearSender(baseline, 1.0, 5.0),
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
// Expect no match because the noise level is too high to establish a reliable correlation.
|
|
|
|
|
if match {
|
|
|
|
|
t.Fatalf("Expected no match due to extreme noise. Reason: %s", reason)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestAlternatingSequences(t *testing.T) {
|
|
|
|
|
baseline := 0.0
|
|
|
|
|
var generatedDelays []float64
|
|
|
|
|
reqSender := func(delay int) (float64, error) {
|
|
|
|
|
generatedDelays = append(generatedDelays, float64(delay))
|
|
|
|
|
return float64(delay), nil
|
|
|
|
|
}
|
|
|
|
|
match, reason, err := checkTimingDependency(
|
|
|
|
|
4, // requestsLimit
|
|
|
|
|
15, // highSleepTimeSeconds
|
|
|
|
|
0.1, // correlationErrorRange
|
|
|
|
|
0.2, // slopeErrorRange
|
|
|
|
|
baseline,
|
|
|
|
|
reqSender,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if !match {
|
|
|
|
|
t.Fatalf("Expected a match but got none. Reason: %s", reason)
|
|
|
|
|
}
|
|
|
|
|
// Verify alternating sequence of delays
|
|
|
|
|
expectedDelays := []float64{15, 3, 15, 3}
|
|
|
|
|
if !reflect.DeepEqual(generatedDelays, expectedDelays) {
|
|
|
|
|
t.Fatalf("Expected delays %v but got %v", expectedDelays, generatedDelays)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNonInjectableQuickFail(t *testing.T) {
|
|
|
|
|
baseline := 0.5
|
|
|
|
|
var timesCalled int
|
|
|
|
|
reqSender := func(delay int) (float64, error) {
|
|
|
|
|
timesCalled++
|
|
|
|
|
return 0.5, nil // Return value less than delay
|
|
|
|
|
}
|
|
|
|
|
match, _, err := checkTimingDependency(
|
|
|
|
|
4, // requestsLimit
|
|
|
|
|
15, // highSleepTimeSeconds
|
|
|
|
|
0.1, // correlationErrorRange
|
|
|
|
|
0.2, // slopeErrorRange
|
|
|
|
|
baseline,
|
|
|
|
|
reqSender,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if match {
|
|
|
|
|
t.Fatal("Expected no match for non-injectable case")
|
|
|
|
|
}
|
|
|
|
|
if timesCalled != 1 {
|
|
|
|
|
t.Fatalf("Expected quick fail after 1 call, got %d calls", timesCalled)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSlowNonInjectableCase(t *testing.T) {
|
|
|
|
|
baseline := 10.0
|
|
|
|
|
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
|
var timesCalled int
|
|
|
|
|
reqSender := func(delay int) (float64, error) {
|
|
|
|
|
timesCalled++
|
|
|
|
|
return 10 + rng.Float64()*0.5, nil
|
|
|
|
|
}
|
|
|
|
|
match, _, err := checkTimingDependency(
|
|
|
|
|
4, // requestsLimit
|
|
|
|
|
15, // highSleepTimeSeconds
|
|
|
|
|
0.1, // correlationErrorRange
|
|
|
|
|
0.2, // slopeErrorRange
|
|
|
|
|
baseline,
|
|
|
|
|
reqSender,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if match {
|
|
|
|
|
t.Fatal("Expected no match for slow non-injectable case")
|
|
|
|
|
}
|
|
|
|
|
if timesCalled > 3 {
|
|
|
|
|
t.Fatalf("Expected early termination (≤3 calls), got %d calls", timesCalled)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRealWorldNonInjectableCase(t *testing.T) {
|
|
|
|
|
baseline := 0.0
|
|
|
|
|
var iteration int
|
|
|
|
|
counts := []float64{11, 21, 11, 21, 11}
|
|
|
|
|
reqSender := func(delay int) (float64, error) {
|
|
|
|
|
iteration++
|
|
|
|
|
return counts[iteration-1], nil
|
|
|
|
|
}
|
|
|
|
|
match, _, err := checkTimingDependency(
|
|
|
|
|
4, // requestsLimit
|
|
|
|
|
15, // highSleepTimeSeconds
|
|
|
|
|
0.1, // correlationErrorRange
|
|
|
|
|
0.2, // slopeErrorRange
|
|
|
|
|
baseline,
|
|
|
|
|
reqSender,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if match {
|
|
|
|
|
t.Fatal("Expected no match for real-world non-injectable case")
|
|
|
|
|
}
|
|
|
|
|
if iteration > 4 {
|
|
|
|
|
t.Fatalf("Expected ≤4 iterations, got %d", iteration)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSmallErrorDependence(t *testing.T) {
|
|
|
|
|
baseline := 0.0
|
|
|
|
|
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
|
reqSender := func(delay int) (float64, error) {
|
|
|
|
|
return float64(delay) + rng.Float64()*0.5, nil
|
|
|
|
|
}
|
|
|
|
|
match, reason, err := checkTimingDependency(
|
|
|
|
|
4, // requestsLimit
|
|
|
|
|
15, // highSleepTimeSeconds
|
|
|
|
|
0.1, // correlationErrorRange
|
|
|
|
|
0.2, // slopeErrorRange
|
|
|
|
|
baseline,
|
|
|
|
|
reqSender,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if !match {
|
|
|
|
|
t.Fatalf("Expected match for small error case. Reason: %s", reason)
|
|
|
|
|
}
|
2024-11-19 11:51:32 +05:30
|
|
|
}
|