/*
 * Copyright 2021 CloudWeGo Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package retry

import (
	"context"
	"reflect"
	"testing"

	"github.com/bytedance/sonic"

	"github.com/cloudwego/kitex/internal/test"
	"github.com/cloudwego/kitex/pkg/rpcinfo"
	"github.com/cloudwego/kitex/pkg/rpcinfo/remoteinfo"
	"github.com/cloudwego/kitex/pkg/serviceinfo"
	"github.com/cloudwego/kitex/pkg/stats"
)

const (
	method = "test"
)

// test new failurePolicy
func TestFailureRetryPolicy(t *testing.T) {
	fp := NewFailurePolicy()

	// case 1
	fp.WithMaxRetryTimes(3)
	jsonRet, err := sonic.MarshalString(fp)
	test.Assert(t, err == nil, err)

	var fp2 FailurePolicy
	err = sonic.UnmarshalString(jsonRet, &fp2)
	test.Assert(t, err == nil, err)
	test.Assert(t, fp.Equals(&fp2))

	// case 2
	fp.WithMaxRetryTimes(2)
	fp.WithRetrySameNode()
	fp.WithFixedBackOff(10)
	jsonRet, err = sonic.MarshalString(fp)
	test.Assert(t, err == nil, err)

	// case 3
	var fp3 FailurePolicy
	err = sonic.UnmarshalString(jsonRet, &fp3)
	test.Assert(t, err == nil, err)
	test.Assert(t, fp.Equals(&fp3), fp3)

	// case 4
	fp.WithRetrySameNode()
	fp.WithRandomBackOff(10, 20)
	jsonRet, err = sonic.MarshalString(fp)
	test.Assert(t, err == nil, err)

	var fp4 FailurePolicy
	err = sonic.UnmarshalString(jsonRet, &fp4)
	test.Assert(t, err == nil, err)
	test.Assert(t, fp.Equals(&fp4), fp4)

	// case 5
	fp.WithRetryBreaker(0.1)
	fp.WithDDLStop()
	fp.WithMaxDurationMS(100)
	jsonRet, err = sonic.MarshalString(fp)
	test.Assert(t, err == nil, err)

	var fp5 FailurePolicy
	err = sonic.UnmarshalString(jsonRet, &fp5)
	test.Assert(t, err == nil, err)
	test.Assert(t, fp.Equals(&fp5), fp5)

	// case 6
	fp = &FailurePolicy{
		StopPolicy: StopPolicy{
			MaxRetryTimes:    2,
			DisableChainStop: false,
			CBPolicy: CBPolicy{
				ErrorRate: defaultCBErrRate,
			},
		},
		Extra: "{}",
	}
	jsonRet, err = sonic.MarshalString(fp)
	test.Assert(t, err == nil, err)
	var fp6 FailurePolicy
	err = sonic.UnmarshalString(jsonRet, &fp6)
	test.Assert(t, err == nil, err)
	test.Assert(t, fp6.BackOffPolicy == nil)
	test.Assert(t, fp.Equals(&fp6), fp6)

	// case 7
	fp.DisableChainRetryStop()
	jsonRet, err = sonic.MarshalString(fp)
	test.Assert(t, err == nil, err)
	var fp7 FailurePolicy
	err = sonic.UnmarshalString(jsonRet, &fp7)
	test.Assert(t, err == nil, err)
	test.Assert(t, fp7.BackOffPolicy == nil)
	test.Assert(t, fp.Equals(&fp7), fp7)

	// case 8
	test.Assert(t, fp.String() == "{StopPolicy:{MaxRetryTimes:2 MaxDurationMS:0 DisableChainStop:true "+
		"DDLStop:false CBPolicy:{ErrorRate:0.1}} BackOffPolicy:<nil> RetrySameNode:false ShouldResultRetry:{ErrorRetry:false, RespRetry:false}}", fp)

	// case 9
	fp.WithSpecifiedResultRetry(&ShouldResultRetry{ErrorRetry: func(err error, ri rpcinfo.RPCInfo) bool {
		return false
	}})
	fp.convertResultRetry()
	test.Assert(t, fp.String() == "{StopPolicy:{MaxRetryTimes:2 MaxDurationMS:0 DisableChainStop:true "+
		"DDLStop:false CBPolicy:{ErrorRate:0.1}} BackOffPolicy:<nil> RetrySameNode:false ShouldResultRetry:{ErrorRetry:true, RespRetry:false}}", fp)
	jsonRet, err = sonic.MarshalString(fp)
	test.Assert(t, err == nil, err)
	var fp9 FailurePolicy
	err = sonic.UnmarshalString(jsonRet, &fp9)
	test.Assert(t, err == nil, err)
	test.Assert(t, fp.Equals(&fp9), fp9)
	test.Assert(t, fp9.ShouldResultRetry == nil)
}

// test new failurePolicy with result retry
func TestFailureRetryPolicyWithResultRetry(t *testing.T) {
	fp := NewFailurePolicyWithResultRetry(&ShouldResultRetry{RespRetry: func(resp interface{}, ri rpcinfo.RPCInfo) bool {
		return false
	}, ErrorRetry: func(err error, ri rpcinfo.RPCInfo) bool {
		return false
	}})
	fp.convertResultRetry()

	test.Assert(t, fp.String() == "{StopPolicy:{MaxRetryTimes:2 MaxDurationMS:0 DisableChainStop:false DDLStop:false "+
		"CBPolicy:{ErrorRate:0.1}} BackOffPolicy:&{BackOffType:none CfgItems:map[]} RetrySameNode:false ShouldResultRetry:{ErrorRetry:true, RespRetry:true}}", fp)
	jsonRet, err := sonic.MarshalString(fp)
	test.Assert(t, err == nil, err)
	var fp10 FailurePolicy
	err = sonic.UnmarshalString(jsonRet, &fp10)
	test.Assert(t, err == nil, err)
	test.Assert(t, fp.Equals(&fp10), fp10)
	test.Assert(t, fp10.ShouldResultRetry == nil)

	t.Run("not-equal-extra", func(t *testing.T) {
		p1 := &FailurePolicy{
			Extra: "1",
		}
		p2 := &FailurePolicy{
			Extra: "2",
		}
		test.Assert(t, !p1.Equals(p2))
	})
}

// test new backupPolicy
func TestBackupRequest(t *testing.T) {
	bp := NewBackupPolicy(20)

	// case 1
	bp.WithMaxRetryTimes(2)
	jsonRet, err := sonic.MarshalString(bp)
	test.Assert(t, err == nil, err)

	var bp2 BackupPolicy
	err = sonic.UnmarshalString(jsonRet, &bp2)
	test.Assert(t, err == nil, err)
	test.Assert(t, bp.Equals(&bp2))

	// case 2
	bp.DisableChainRetryStop()
	jsonRet, err = sonic.MarshalString(bp)
	test.Assert(t, err == nil, err)

	var bp3 BackupPolicy
	err = sonic.UnmarshalString(jsonRet, &bp3)
	test.Assert(t, err == nil, err)
	test.Assert(t, bp.Equals(&bp3))
}

// test new policy both not nil
func TestRetryPolicyBothNotNil(t *testing.T) {
	p := Policy{
		Enable:        true,
		FailurePolicy: NewFailurePolicy(),
		BackupPolicy:  NewBackupPolicy(20),
	}
	ctx := context.Background()
	jsonRet, err := sonic.MarshalString(p)
	test.Assert(t, err == nil, err)

	var p2 Policy
	err = sonic.UnmarshalString(jsonRet, &p2)
	test.Assert(t, err == nil, err)
	test.Assert(t, p2.Enable == true)
	test.Assert(t, p.Equals(p2))

	ri := genRPCInfo()
	rc := Container{}
	rc.NotifyPolicyChange(ri.To().Method(), p2)

	r := rc.getRetryer(ctx, ri)
	fr, ok := r.(*failureRetryer)
	test.Assert(t, ok)
	test.Assert(t, fr.enable)
	test.Assert(t, fr.policy.Equals(p.FailurePolicy), fr.policy, p.FailurePolicy)

	p2.Enable = false
	rc.NotifyPolicyChange(ri.To().Method(), p2)
	test.Assert(t, !fr.enable)
}

// test new policy both nil
func TestRetryPolicyBothNil(t *testing.T) {
	p := Policy{}
	jsonRet, err := sonic.MarshalString(p)
	test.Assert(t, err == nil, err)

	var p2 Policy
	err = sonic.UnmarshalString(jsonRet, &p2)
	test.Assert(t, err == nil, err)
	test.Assert(t, p.Equals(p2))

	ri := genRPCInfo()
	rc := Container{}
	rc.NotifyPolicyChange(ri.To().Method(), p2)

	r := rc.getRetryer(context.Background(), ri)
	test.Assert(t, r != nil, r)
}

// test failurePolicy update
func TestRetryPolicyFailure(t *testing.T) {
	p := Policy{
		Enable:        true,
		FailurePolicy: NewFailurePolicy(),
	}
	jsonRet := `{"enable":true,"type":0,"failure_policy":{"stop_policy":{"max_retry_times":2,"max_duration_ms":0,"disable_chain_stop":false,"ddl_stop":false,"cb_policy":{"error_rate":0.1,"min_sample":200}},"backoff_policy":{"backoff_type":"none"},"retry_same_node":false}}`
	var p2 Policy
	err := sonic.UnmarshalString(jsonRet, &p2)
	test.Assert(t, err == nil, err)
	test.Assert(t, p2.Enable)
	test.Assert(t, p.Equals(p2))
	test.Assert(t, p.FailurePolicy.StopPolicy.CBPolicy.ErrorRate == 0.1)

	ri := genRPCInfo()
	rc := Container{}
	rc.NotifyPolicyChange(ri.To().Method(), p2)
	r := rc.getRetryer(context.Background(), ri)
	fr, ok := r.(*failureRetryer)
	test.Assert(t, ok)
	test.Assert(t, fr.policy.Equals(p.FailurePolicy))

	// 模拟配置变更
	fp := NewFailurePolicy()
	fp.WithMaxRetryTimes(1)
	fp.WithMaxRetryTimes(2)
	fp.WithRetrySameNode()
	fp.WithFixedBackOff(10)
	p = Policy{
		Enable:        true,
		FailurePolicy: fp,
	}
	jsonRet, err = sonic.MarshalString(p)
	test.Assert(t, err == nil, err)

	var p3 Policy
	err = sonic.UnmarshalString(jsonRet, &p3)
	test.Assert(t, err == nil, err)
	test.Assert(t, p.Equals(p3))

	// 更新配置
	rc.NotifyPolicyChange(ri.To().Method(), p3)
	r = rc.getRetryer(context.Background(), ri)
	fr, ok = r.(*failureRetryer)
	test.Assert(t, ok)
	test.Assert(t, fr.policy.Equals(p.FailurePolicy))
}

// test policy not equal
func TestPolicyNotEqual(t *testing.T) {
	var p, policy Policy

	failurePolicy := FailurePolicy{
		StopPolicy: StopPolicy{
			MaxRetryTimes:    1,
			MaxDurationMS:    2,
			DisableChainStop: false,
			DDLStop:          false,
			CBPolicy: CBPolicy{
				ErrorRate: defaultCBErrRate,
			},
		},
		BackOffPolicy: &BackOffPolicy{
			BackOffType: FixedBackOffType,
			CfgItems: map[BackOffCfgKey]float64{
				FixMSBackOffCfgKey: 100.0,
			},
		},
		RetrySameNode: false,
	}

	// create failurePolicy
	policy = Policy{
		Enable:        true,
		Type:          FailureType,
		FailurePolicy: &failurePolicy,
	}
	jsonRet, err := sonic.MarshalString(policy)
	test.Assert(t, err == nil, err)

	// case1 enable not equal
	err = sonic.UnmarshalString(jsonRet, &p)
	test.Assert(t, err == nil, err)
	p.Enable = false
	test.Assert(t, !p.Equals(policy))

	// case2 type not equal
	err = sonic.UnmarshalString(jsonRet, &p)
	test.Assert(t, err == nil, err)
	p.Type = BackupType
	test.Assert(t, !p.Equals(policy))

	// case3 failurePolicy not equal
	err = sonic.UnmarshalString(jsonRet, &p)
	test.Assert(t, err == nil, err)
	p.FailurePolicy = nil
	test.Assert(t, !p.Equals(policy))
	test.Assert(t, !policy.Equals(p))

	// case4 failurePolicy stopPolicy not equal
	err = sonic.UnmarshalString(jsonRet, &p)
	test.Assert(t, err == nil, err)
	p.FailurePolicy.StopPolicy.MaxRetryTimes = 2
	test.Assert(t, !p.Equals(policy))

	// case5 failurePolicy backOffPolicy not equal
	err = sonic.UnmarshalString(jsonRet, &p)
	test.Assert(t, err == nil, err)
	p.FailurePolicy.BackOffPolicy = nil
	test.Assert(t, !p.Equals(policy))
	test.Assert(t, !policy.Equals(p))

	// case6 failurePolicy backOffPolicy backOffType not equal
	err = sonic.UnmarshalString(jsonRet, &p)
	test.Assert(t, err == nil, err)
	p.FailurePolicy.BackOffPolicy.BackOffType = RandomBackOffType
	test.Assert(t, !p.Equals(policy))

	// case7 failurePolicy backOffPolicy len(cfgItems) not equal
	err = sonic.UnmarshalString(jsonRet, &p)
	test.Assert(t, err == nil, err)
	p.FailurePolicy.BackOffPolicy.CfgItems[MinMSBackOffCfgKey] = 100
	test.Assert(t, !p.Equals(policy))

	// case8 failurePolicy backOffPolicy cfgItems not equal
	p = Policy{}
	err = sonic.UnmarshalString(jsonRet, &p)
	test.Assert(t, err == nil, err)
	p.FailurePolicy.BackOffPolicy.CfgItems[FixMSBackOffCfgKey] = 101
	test.Assert(t, !p.Equals(policy))

	// case9 failurePolicy retrySameNode not equal
	err = sonic.UnmarshalString(jsonRet, &p)
	test.Assert(t, err == nil, err)
	p.FailurePolicy.RetrySameNode = true
	test.Assert(t, !p.Equals(policy))

	// create backupPolicy
	policy = Policy{
		Enable: true,
		Type:   BackupType,
		BackupPolicy: &BackupPolicy{
			RetryDelayMS: 1,
			StopPolicy: StopPolicy{
				MaxRetryTimes:    2,
				MaxDurationMS:    3,
				DisableChainStop: false,
				DDLStop:          false,
				CBPolicy: CBPolicy{
					ErrorRate: defaultCBErrRate,
				},
			},
			RetrySameNode: false,
		},
	}
	jsonRet, err = sonic.MarshalString(policy)
	test.Assert(t, err == nil, err)

	// case10 backupPolicy not equal
	p = Policy{}
	err = sonic.UnmarshalString(jsonRet, &p)
	test.Assert(t, err == nil, err)
	p.BackupPolicy = nil
	test.Assert(t, !p.Equals(policy))
	test.Assert(t, !policy.Equals(p))

	// case11 backupPolicy retryDelayMS not equal
	err = sonic.UnmarshalString(jsonRet, &p)
	test.Assert(t, err == nil, err)
	p.BackupPolicy.RetryDelayMS = 2
	test.Assert(t, !p.Equals(policy))

	// case12 backupPolicy stopPolicy not equal
	err = sonic.UnmarshalString(jsonRet, &p)
	test.Assert(t, err == nil, err)
	p.BackupPolicy.StopPolicy.MaxRetryTimes = 3
	test.Assert(t, !p.Equals(policy))

	// case13 backupPolicy retrySameNode not equal
	err = sonic.UnmarshalString(jsonRet, &p)
	test.Assert(t, err == nil, err)
	p.BackupPolicy.RetrySameNode = true
	test.Assert(t, !p.Equals(policy))

	// create mixed policy
	policy = Policy{
		Enable: true,
		Type:   MixedType,
		MixedPolicy: &MixedPolicy{
			RetryDelayMS:  1,
			FailurePolicy: failurePolicy,
		},
	}
	jsonRet, err = sonic.MarshalString(policy)
	test.Assert(t, err == nil, err)

	// case14 mixedPolicy not equal
	p = Policy{}
	err = sonic.UnmarshalString(jsonRet, &p)
	test.Assert(t, err == nil, err)
	p.MixedPolicy = nil
	test.Assert(t, !p.Equals(policy))
	test.Assert(t, !policy.Equals(p))

	// case15 mixedPolicy retryDelayMS not equal
	err = sonic.UnmarshalString(jsonRet, &p)
	test.Assert(t, err == nil, err)
	p.MixedPolicy.RetryDelayMS = 2
	test.Assert(t, !p.Equals(policy))

	// case16 mixedPolicy failurePolicy not equal
	err = sonic.UnmarshalString(jsonRet, &p)
	test.Assert(t, err == nil, err)
	p.MixedPolicy.FailurePolicy = FailurePolicy{}
	test.Assert(t, !p.Equals(policy))
}

func TestPolicyNotRetryForTimeout(t *testing.T) {
	// create failurePolicy
	fp := &FailurePolicy{StopPolicy: StopPolicy{
		MaxRetryTimes:    1,
		MaxDurationMS:    2,
		DisableChainStop: false,
		DDLStop:          false,
		CBPolicy: CBPolicy{
			ErrorRate: defaultCBErrRate,
		},
	}}
	// case 1: ShouldResultRetry is nil, retry for timeout
	test.Assert(t, fp.isRetryForTimeout())

	// case 2: ShouldResultRetry is not nil, NotRetryForTimeout is false, retry for timeout
	fp.ShouldResultRetry = &ShouldResultRetry{
		ErrorRetry: nil,
		RespRetry:  nil,
	}

	// case 3: ShouldResultRetry is not nil, NotRetryForTimeout is true, not retry for timeout
	fp.ShouldResultRetry.NotRetryForTimeout = true
	test.Assert(t, !fp.isRetryForTimeout())
}

func genRPCInfo(newResultFunc ...func() interface{}) rpcinfo.RPCInfo {
	to := remoteinfo.NewRemoteInfo(&rpcinfo.EndpointBasicInfo{Method: method}, method).ImmutableView()
	riStats := rpcinfo.AsMutableRPCStats(rpcinfo.NewRPCStats())
	riStats.SetLevel(stats.LevelDetailed)
	ri := rpcinfo.NewRPCInfo(to, to, rpcinfo.NewInvocation("", method), rpcinfo.NewRPCConfig(), riStats.ImmutableView())
	setter := ri.Invocation().(rpcinfo.InvocationSetter)
	if len(newResultFunc) > 0 {
		setter.SetMethodInfo(serviceinfo.NewMethodInfo(nil, nil, newResultFunc[0], false))
	} else {
		setter.SetMethodInfo(serviceinfo.NewMethodInfo(nil, nil, func() interface{} {
			return nil
		}, false))
	}
	return ri
}

func genRPCInfoWithFirstStats(firstRI rpcinfo.RPCInfo) rpcinfo.RPCInfo {
	to := remoteinfo.NewRemoteInfo(&rpcinfo.EndpointBasicInfo{Method: method}, method).ImmutableView()
	riStats := rpcinfo.AsMutableRPCStats(firstRI.Stats().CopyForRetry())
	riStats.SetLevel(stats.LevelDetailed)
	ri := rpcinfo.NewRPCInfo(to, to, rpcinfo.NewInvocation("", method), rpcinfo.NewRPCConfig(), riStats.ImmutableView())
	return ri
}

func genRPCInfoWithRemoteTag(tags map[string]string) rpcinfo.RPCInfo {
	to := remoteinfo.NewRemoteInfo(&rpcinfo.EndpointBasicInfo{Method: method, Tags: tags}, method).ImmutableView()
	ri := rpcinfo.NewRPCInfo(to, to, rpcinfo.NewInvocation("", method), rpcinfo.NewRPCConfig(), rpcinfo.NewRPCStats())
	return ri
}

func TestBackOffPolicy_copyCfgItems(t *testing.T) {
	type fields struct {
		BackOffType BackOffType
		CfgItems    map[BackOffCfgKey]float64
	}
	tests := []struct {
		name   string
		fields fields
		want   map[BackOffCfgKey]float64
	}{
		{
			name: "nil_map",
			fields: fields{
				BackOffType: NoneBackOffType,
				CfgItems:    nil,
			},
			want: nil,
		},
		{
			name: "empty_map",
			fields: fields{
				BackOffType: NoneBackOffType,
				CfgItems:    make(map[BackOffCfgKey]float64),
			},
			want: make(map[BackOffCfgKey]float64),
		},
		{
			name: "not_empty_map",
			fields: fields{
				BackOffType: NoneBackOffType,
				CfgItems: map[BackOffCfgKey]float64{
					MinMSBackOffCfgKey: 1,
					MaxMSBackOffCfgKey: 2,
				},
			},
			want: map[BackOffCfgKey]float64{
				MinMSBackOffCfgKey: 1,
				MaxMSBackOffCfgKey: 2,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p := &BackOffPolicy{
				BackOffType: tt.fields.BackOffType,
				CfgItems:    tt.fields.CfgItems,
			}
			if got := p.copyCfgItems(); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("copyCfgItems() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestBackOffPolicy_DeepCopy(t *testing.T) {
	type fields struct {
		p *BackOffPolicy
	}
	tests := []struct {
		name   string
		fields fields
		want   *BackOffPolicy
	}{
		{
			name: "nil_policy",
			fields: fields{
				p: nil,
			},
			want: nil,
		},
		{
			name: "empty_policy",
			fields: fields{
				p: &BackOffPolicy{
					BackOffType: NoneBackOffType,
					CfgItems:    make(map[BackOffCfgKey]float64),
				},
			},
			want: &BackOffPolicy{
				BackOffType: NoneBackOffType,
				CfgItems:    make(map[BackOffCfgKey]float64),
			},
		},
		{
			name: "not_empty_policy",
			fields: fields{
				p: &BackOffPolicy{
					BackOffType: NoneBackOffType,
					CfgItems: map[BackOffCfgKey]float64{
						MinMSBackOffCfgKey: 1,
						MaxMSBackOffCfgKey: 2,
					},
				},
			},
			want: &BackOffPolicy{
				BackOffType: NoneBackOffType,
				CfgItems: map[BackOffCfgKey]float64{
					MinMSBackOffCfgKey: 1,
					MaxMSBackOffCfgKey: 2,
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := tt.fields.p.DeepCopy(); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("DeepCopy() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestBackupPolicy_DeepCopy(t *testing.T) {
	type fields struct {
		p *BackupPolicy
	}
	tests := []struct {
		name   string
		fields fields
		want   *BackupPolicy
	}{
		{
			name: "nil_policy",
			fields: fields{
				p: nil,
			},
			want: nil,
		},
		{
			name: "empty_policy",
			fields: fields{
				p: &BackupPolicy{
					RetryDelayMS:  0,
					StopPolicy:    StopPolicy{},
					RetrySameNode: false,
				},
			},
			want: &BackupPolicy{
				RetryDelayMS:  0,
				StopPolicy:    StopPolicy{},
				RetrySameNode: false,
			},
		},
		{
			name: "not_empty_policy",
			fields: fields{
				p: &BackupPolicy{
					RetryDelayMS:  1,
					StopPolicy:    StopPolicy{},
					RetrySameNode: true,
				},
			},
			want: &BackupPolicy{
				RetryDelayMS:  1,
				StopPolicy:    StopPolicy{},
				RetrySameNode: true,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p := tt.fields.p
			if got := p.DeepCopy(); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("DeepCopy() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestFailurePolicy_DeepCopy(t *testing.T) {
	type fields struct {
		p *FailurePolicy
	}
	tests := []struct {
		name   string
		fields fields
		want   *FailurePolicy
	}{
		{
			name: "nil_policy",
			fields: fields{
				p: nil,
			},
			want: nil,
		},
		{
			name: "empty_policy",
			fields: fields{
				p: &FailurePolicy{},
			},
			want: &FailurePolicy{},
		},
		{
			name: "not_empty_policy",
			fields: fields{
				p: &FailurePolicy{
					StopPolicy:        StopPolicy{},
					BackOffPolicy:     &BackOffPolicy{},
					RetrySameNode:     true,
					ShouldResultRetry: &ShouldResultRetry{},
					Extra:             "{}",
				},
			},
			want: &FailurePolicy{
				StopPolicy:        StopPolicy{},
				BackOffPolicy:     &BackOffPolicy{},
				RetrySameNode:     true,
				ShouldResultRetry: &ShouldResultRetry{},
				Extra:             "{}",
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := tt.fields.p.DeepCopy(); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("DeepCopy() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestPolicy_DeepCopy(t *testing.T) {
	type fields struct {
		p *Policy
	}
	tests := []struct {
		name   string
		fields fields
		want   *Policy
	}{
		{
			name: "nil_policy",
			fields: fields{
				p: nil,
			},
			want: nil,
		},
		{
			name: "empty_policy",
			fields: fields{
				p: &Policy{},
			},
			want: &Policy{},
		},
		{
			name: "not_empty_policy",
			fields: fields{
				p: &Policy{
					Enable: true,
					Type:   BackupType,
					FailurePolicy: &FailurePolicy{
						RetrySameNode: true,
						Extra:         "{}",
					},
					BackupPolicy: &BackupPolicy{
						RetryDelayMS: 1000,
					},
				},
			},
			want: &Policy{
				Enable: true,
				Type:   BackupType,
				FailurePolicy: &FailurePolicy{
					RetrySameNode: true,
					Extra:         "{}",
				},
				BackupPolicy: &BackupPolicy{
					RetryDelayMS: 1000,
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := tt.fields.p.DeepCopy(); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("DeepCopy() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestCheckStopPolicy(t *testing.T) {
	mp := NewMixedPolicy(100)
	err := checkStopPolicy(&mp.StopPolicy, maxMixRetryTimes, &mixedRetryer{})
	test.Assert(t, err == nil, err)

	mp.StopPolicy.MaxRetryTimes = -1
	err = checkStopPolicy(&mp.StopPolicy, maxMixRetryTimes, &mixedRetryer{})
	test.Assert(t, err != nil, err)
	test.Assert(t, err.Error() == "invalid MaxRetryTimes[-1]")

	mp.StopPolicy.MaxRetryTimes = 5
	err = checkStopPolicy(&mp.StopPolicy, maxMixRetryTimes, &mixedRetryer{})
	test.Assert(t, err != nil, err)
	test.Assert(t, err.Error() == "invalid MaxRetryTimes[5]")
	mp.StopPolicy.MaxRetryTimes = maxMixRetryTimes

	mp.StopPolicy.CBPolicy.ErrorRate = 0.5
	err = checkStopPolicy(&mp.StopPolicy, maxMixRetryTimes, &mixedRetryer{})
	test.Assert(t, err == nil, err)
	test.Assert(t, mp.StopPolicy.CBPolicy.ErrorRate == defaultCBErrRate)

	mp.StopPolicy.CBPolicy.ErrorRate = -0.1
	err = checkStopPolicy(&mp.StopPolicy, maxMixRetryTimes, &mixedRetryer{})
	test.Assert(t, err == nil, err)
	test.Assert(t, mp.StopPolicy.CBPolicy.ErrorRate == defaultCBErrRate)
}
