@@ -16,6 +16,7 @@ import (
1616 "testing"
1717 "time"
1818
19+ sgbucket "github.com/couchbase/sg-bucket"
1920 "github.com/couchbase/sync_gateway/base"
2021 "github.com/couchbase/sync_gateway/db"
2122 "github.com/couchbase/sync_gateway/rest"
@@ -2212,6 +2213,78 @@ func TestActiveReplicatorV4DefaultResolverWithTombstoneRemote(t *testing.T) {
22122213 })
22132214}
22142215
2216+ // TestDefaultConflictResolverMatchingTimestamps verifies that default (HLV-aware) conflict resolution for different
2217+ // source IDs but matching versions is local wins. See CBG-5570 for changing this.
2218+ func TestDefaultConflictResolverMatchingTimestamps (t * testing.T ) {
2219+ base .LongRunningTest (t )
2220+
2221+ base .RequireNumTestBuckets (t , 2 )
2222+
2223+ // Matching-timestamps ties are an HLV/V4-only scenario (V3/revTree conflicts are resolved by generation and
2224+ // digest, not by timestamp), so only the V4 subtest is run.
2225+ sgrRunner := rest .NewSGRTestRunner (t )
2226+ sgrRunner .RunSubprotocolV4 (func (t * testing.T ) {
2227+ activeRT , passiveRT , remoteDBURLString := sgrRunner .SetupSGRPeers (t )
2228+ remoteURL , err := url .Parse (remoteDBURLString )
2229+ require .NoError (t , err )
2230+ activeRTCtx := activeRT .Context ()
2231+
2232+ fixedTime := sgbucket .HLCWallClock ()
2233+ tieClock := func () uint64 { return fixedTime }
2234+ activeRT .GetDatabase ().SetHLCClockForTest (tieClock )
2235+ passiveRT .GetDatabase ().SetHLCClockForTest (tieClock )
2236+
2237+ docID := "doc"
2238+ activeRTVersion := createDoc (activeRT , docID , "local" )
2239+ passiveRTVersion := createDoc (passiveRT , docID , "remote" )
2240+ require .Equal (t , activeRTVersion .CV .Value , passiveRTVersion .CV .Value , "test requires activeRT and passiveRT to generate matching HLC timestamps" )
2241+ require .NotEqual (t , activeRTVersion .CV .SourceID , passiveRTVersion .CV .SourceID )
2242+
2243+ replicationStats := dbReplicatorStats (t )
2244+ config := db.ActiveReplicatorConfig {
2245+ ID : t .Name (),
2246+ Direction : db .ActiveReplicatorTypePushAndPull ,
2247+ RemoteDBURL : remoteURL ,
2248+ ActiveDB : & db.Database {
2249+ DatabaseContext : activeRT .GetDatabase (),
2250+ },
2251+ Continuous : true ,
2252+ ConflictResolverFuncForHLV : db .DefaultLWWConflictResolutionType ,
2253+ ReplicationStatsMap : replicationStats ,
2254+ CollectionsEnabled : ! activeRT .GetDatabase ().OnlyDefaultCollection (),
2255+ SupportedBLIPProtocols : sgrRunner .SupportedSubprotocols ,
2256+ }
2257+
2258+ // Create active replicator and start replication.
2259+ ar , err := db .NewActiveReplicator (activeRTCtx , & config )
2260+ require .NoError (t , err )
2261+ require .NoError (t , ar .Start (activeRTCtx ), "Error starting replication" )
2262+ defer func () { require .NoError (t , ar .Stop (), "Error stopping replication" ) }()
2263+
2264+ requireConflictResolvedLocally := func (t * testing.T , rt * rest.RestTester ) {
2265+ t .Helper ()
2266+ collection , ctx := rt .GetSingleTestDatabaseCollectionWithUser ()
2267+ require .EventuallyWithT (t , func (c * assert.CollectT ) {
2268+ doc , err := collection .GetDocument (ctx , docID , db .DocUnmarshalAll )
2269+ if ! assert .NoError (c , err ) {
2270+ return
2271+ }
2272+ if ! assert .NotNil (c , doc .HLV ) {
2273+ return
2274+ }
2275+ assert .Equal (c , activeRTVersion .CV .SourceID , doc .HLV .SourceID , "expected local (activeRT) source to win the tie" )
2276+ assert .Equal (c , "local" , doc .Body (base .TestCtx (t ))["key" ], "expected local (activeRT) body to win the tie" )
2277+ }, 10 * time .Second , 100 * time .Millisecond )
2278+ }
2279+ requireConflictResolvedLocally (t , activeRT )
2280+ requireConflictResolvedLocally (t , passiveRT )
2281+
2282+ base .RequireWaitForStat (t , replicationStats .ConflictResolvedLocalCount .Value , 1 )
2283+ base .RequireWaitForStat (t , replicationStats .ConflictResolvedMergedCount .Value , 0 )
2284+ base .RequireWaitForStat (t , replicationStats .ConflictResolvedRemoteCount .Value , 0 )
2285+ })
2286+ }
2287+
22152288// getRevTreeID create a revtree ID for a new revision that is a child of the parentRevID for a given body.
22162289func getRevTreeID (t * testing.T , parentRevID string , body []byte ) string {
22172290 prevGeneration , _ := db .ParseRevID (t .Context (), parentRevID )
0 commit comments