From 4d31726621ac4196e8745c761c408280782c10a5 Mon Sep 17 00:00:00 2001 From: Sean Quah Date: Fri, 10 Mar 2023 23:34:05 +0000 Subject: [PATCH 1/4] Add `MustJoinRoom` option to allow Complement to do a partial state join Some of the faster joins tests have two Complement homeservers in the same room. We need both Complement homeservers to believe they are in the room, so that we can wait to observe a leave for the test user as part of test cleanup. The easiest way to do this is to have the second Complement homeserver perform a real join. Signed-off-by: Sean Quah --- internal/federation/server.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/federation/server.go b/internal/federation/server.go index ed7974d1..7959619d 100644 --- a/internal/federation/server.go +++ b/internal/federation/server.go @@ -339,7 +339,7 @@ func (s *Server) MustCreateEvent(t *testing.T, room *ServerRoom, ev b.Event) *go // MustJoinRoom will make the server send a make_join and a send_join to join a room // It returns the resultant room. -func (s *Server) MustJoinRoom(t *testing.T, deployment *docker.Deployment, remoteServer gomatrixserverlib.ServerName, roomID string, userID string) *ServerRoom { +func (s *Server) MustJoinRoom(t *testing.T, deployment *docker.Deployment, remoteServer gomatrixserverlib.ServerName, roomID string, userID string, partialState ...bool) *ServerRoom { t.Helper() origin := gomatrixserverlib.ServerName(s.serverName) fedClient := s.FederationClient(deployment) @@ -352,7 +352,13 @@ func (s *Server) MustJoinRoom(t *testing.T, deployment *docker.Deployment, remot if err != nil { t.Fatalf("MustJoinRoom: failed to sign event: %v", err) } - sendJoinResp, err := fedClient.SendJoin(context.Background(), origin, remoteServer, joinEvent) + var sendJoinResp gomatrixserverlib.RespSendJoin + if len(partialState) == 0 || !partialState[0] { + // Default to doing a regular join. + sendJoinResp, err = fedClient.SendJoin(context.Background(), origin, remoteServer, joinEvent) + } else { + sendJoinResp, err = fedClient.SendJoinPartialState(context.Background(), origin, remoteServer, joinEvent) + } if err != nil { t.Fatalf("MustJoinRoom: send_join failed: %v", err) } From 5b64af34e00e7ca304c54352283115b2de21fbb2 Mon Sep 17 00:00:00 2001 From: Sean Quah Date: Fri, 10 Mar 2023 21:12:40 +0000 Subject: [PATCH 2/4] Allow Complement homeservers to contact each other This is useful for setting up tests with two Complement homeservers in the same room by having one of them join off the other. Signed-off-by: Sean Quah --- internal/docker/deployer.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/internal/docker/deployer.go b/internal/docker/deployer.go index d63cda73..dfcb9194 100644 --- a/internal/docker/deployer.go +++ b/internal/docker/deployer.go @@ -496,15 +496,23 @@ type RoundTripper struct { func (t *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { // map HS names to localhost:port combos hsName := req.URL.Hostname() - dep, ok := t.Deployment.HS[hsName] - if !ok { - return nil, fmt.Errorf("dockerRoundTripper unknown hostname: '%s'", hsName) - } - newURL, err := url.Parse(dep.FedBaseURL) - if err != nil { - return nil, fmt.Errorf("dockerRoundTripper: failed to parase fedbaseurl for hs: %s", err) + if hsName == t.Deployment.Config.HostnameRunningComplement { + if req.URL.Port() == "" { + req.URL.Host = "localhost" + } else { + req.URL.Host = "localhost:" + req.URL.Port() + } + } else { + dep, ok := t.Deployment.HS[hsName] + if !ok { + return nil, fmt.Errorf("dockerRoundTripper unknown hostname: '%s'", hsName) + } + newURL, err := url.Parse(dep.FedBaseURL) + if err != nil { + return nil, fmt.Errorf("dockerRoundTripper: failed to parase fedbaseurl for hs: %s", err) + } + req.URL.Host = newURL.Host } - req.URL.Host = newURL.Host req.URL.Scheme = "https" transport := &http.Transport{ TLSClientConfig: &tls.Config{ From e7f7e710094b9fb6adebdb6bfb175977abc4b931 Mon Sep 17 00:00:00 2001 From: Sean Quah Date: Tue, 14 Mar 2023 15:51:42 +0000 Subject: [PATCH 3/4] Have the second Complement homeserver join rooms properly ...so that we can wait for the homeserver under test to send us leave events during test cleanup. Signed-off-by: Sean Quah --- ...federation_room_join_partial_state_test.go | 70 ++++++++++++++----- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/tests/federation_room_join_partial_state_test.go b/tests/federation_room_join_partial_state_test.go index f66a56ae..514697ad 100644 --- a/tests/federation_room_join_partial_state_test.go +++ b/tests/federation_room_join_partial_state_test.go @@ -2170,7 +2170,14 @@ func TestPartialStateJoin(t *testing.T) { // The room starts with @charlie:server1 and @derek:server1 in it. // @elsie:server2 joins the room before @t23alice:hs1. - room.AddEvent(createJoinEvent(t, server2, room, server2.UserID("elsie"))) + server2.MustJoinRoom( + t, + deployment, + gomatrixserverlib.ServerName(server1.ServerName()), + room.RoomID, + server2.UserID("elsie"), + true, + ) // @t23alice:hs1 joins the room. psjResult := beginPartialStateJoin(t, server1, room, alice) @@ -2211,10 +2218,15 @@ func TestPartialStateJoin(t *testing.T) { t.Log("@charlie and @derek received device list update.") // @elsie:server2 joins the room. - // Make server1 send the event to the homeserver, since server2's rooms list isn't set - // up right and it can't answer queries about events in the room. - joinEvent := createJoinEvent(t, server2, room, server2.UserID("elsie")) - room.AddEvent(joinEvent) + server2.MustJoinRoom( + t, + deployment, + gomatrixserverlib.ServerName(server1.ServerName()), + room.RoomID, + server2.UserID("elsie"), + true, + ) + joinEvent := room.CurrentState("m.room.member", server2.UserID("elsie")) server1.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{joinEvent.JSON()}, nil) awaitEventViaSync(t, alice, room.RoomID, joinEvent.EventID(), "") @@ -2245,15 +2257,22 @@ func TestPartialStateJoin(t *testing.T) { // The room starts with @charlie:server1 and @derek:server1 in it. // @elsie:server2 joins the room before @t25alice:hs1. - room.AddEvent(createJoinEvent(t, server2, room, server2.UserID("elsie"))) + server2.MustJoinRoom( + t, + deployment, + gomatrixserverlib.ServerName(server1.ServerName()), + room.RoomID, + server2.UserID("elsie"), + true, + ) // @t25alice:hs1 joins the room. psjResult := beginPartialStateJoin(t, server1, room, alice) defer psjResult.Destroy(t) // @elsie:server2 leaves the room. - // Make server1 send the event to the homeserver, since server2's rooms list isn't set - // up right and it can't answer queries about events in the room. + // Create and send the event to the homeserver using server1, since the test setup did + // not give server2 a complete or up to date copy of the room state. leaveEvent := createLeaveEvent(t, server2, room, server2.UserID("elsie")) room.AddEvent(leaveEvent) server1.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{leaveEvent.JSON()}, nil) @@ -2312,10 +2331,15 @@ func TestPartialStateJoin(t *testing.T) { psjResult = beginPartialStateJoin(t, server1, room, alice) // @elsie:server2 joins the room. - // Make server1 send the event to the homeserver, since server2's rooms list isn't set - // up right and it can't answer queries about events in the room. - joinEvent := createJoinEvent(t, server2, room, elsie) - room.AddEvent(joinEvent) + server2.MustJoinRoom( + t, + deployment, + gomatrixserverlib.ServerName(server1.ServerName()), + room.RoomID, + elsie, + true, + ) + joinEvent := room.CurrentState("m.room.member", elsie) server1.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{joinEvent.JSON()}, nil) syncToken = awaitEventViaSync(t, alice, room.RoomID, joinEvent.EventID(), "") @@ -2370,8 +2394,8 @@ func TestPartialStateJoin(t *testing.T) { ) // @elsie:server2 leaves the room. - // Make server1 send the event to the homeserver, since server2's rooms list isn't set - // up right and it can't answer queries about events in the room. + // Make server1 send the event to the homeserver, since the test setup did not give + // server2 a complete or up to date copy of the room state. leaveEvent := createLeaveEvent(t, server2, partialStateRoom, elsie) partialStateRoom.AddEvent(leaveEvent) server1.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{leaveEvent.JSON()}, nil) @@ -2482,7 +2506,14 @@ func TestPartialStateJoin(t *testing.T) { // The room starts with @charlie:server1 and @derek:server1 in it. // @elsie:server2 joins the room, followed by @t28alice:hs1. // server1 does not tell hs1 that server2 is in the room. - room.AddEvent(createJoinEvent(t, server2, room, server2.UserID("elsie"))) + server2.MustJoinRoom( + t, + deployment, + gomatrixserverlib.ServerName(server1.ServerName()), + room.RoomID, + server2.UserID("elsie"), + true, + ) psjResult := beginPartialStateJoin(t, server1, room, alice) defer psjResult.Destroy(t) @@ -2506,7 +2537,14 @@ func TestPartialStateJoin(t *testing.T) { // The room starts with @charlie:server1 and @derek:server1 in it. // @elsie:server2 joins the room, followed by @t29alice:hs1. // server1 does not tell hs1 that server2 is in the room. - room.AddEvent(createJoinEvent(t, server2, room, server2.UserID("elsie"))) + server2.MustJoinRoom( + t, + deployment, + gomatrixserverlib.ServerName(server1.ServerName()), + room.RoomID, + server2.UserID("elsie"), + true, + ) psjResult := beginPartialStateJoin(t, server1, room, alice) defer psjResult.Destroy(t) From 926a40f23c10865220087bce5db0c6b119ab6aa8 Mon Sep 17 00:00:00 2001 From: Sean Quah Date: Tue, 14 Mar 2023 16:19:01 +0000 Subject: [PATCH 4/4] Leave rooms in test cleanup: device list tests This depends on the second Complement homeserver believing it is joined to the test room. Signed-off-by: Sean Quah --- ...federation_room_join_partial_state_test.go | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/tests/federation_room_join_partial_state_test.go b/tests/federation_room_join_partial_state_test.go index 514697ad..586ab7ae 100644 --- a/tests/federation_room_join_partial_state_test.go +++ b/tests/federation_room_join_partial_state_test.go @@ -2170,7 +2170,7 @@ func TestPartialStateJoin(t *testing.T) { // The room starts with @charlie:server1 and @derek:server1 in it. // @elsie:server2 joins the room before @t23alice:hs1. - server2.MustJoinRoom( + server2Room := server2.MustJoinRoom( t, deployment, gomatrixserverlib.ServerName(server1.ServerName()), @@ -2181,7 +2181,7 @@ func TestPartialStateJoin(t *testing.T) { // @t23alice:hs1 joins the room. psjResult := beginPartialStateJoin(t, server1, room, alice) - defer psjResult.Destroy(t) + defer server2.WithWaitForLeave(t, server2Room, alice.UserID, func() { psjResult.Destroy(t) }) // Both homeservers should receive device list updates. renameDevice(t, alice, "A new device name 1") @@ -2218,7 +2218,7 @@ func TestPartialStateJoin(t *testing.T) { t.Log("@charlie and @derek received device list update.") // @elsie:server2 joins the room. - server2.MustJoinRoom( + server2Room := server2.MustJoinRoom( t, deployment, gomatrixserverlib.ServerName(server1.ServerName()), @@ -2226,6 +2226,9 @@ func TestPartialStateJoin(t *testing.T) { server2.UserID("elsie"), true, ) + // NB: We register the `psjResult.Destroy()` cleanup twice. This is alright because it + // is idempotent. Here we wait for server 2 to observe the leave too. + defer server2.WithWaitForLeave(t, server2Room, alice.UserID, func() { psjResult.Destroy(t) }) joinEvent := room.CurrentState("m.room.member", server2.UserID("elsie")) server1.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{joinEvent.JSON()}, nil) awaitEventViaSync(t, alice, room.RoomID, joinEvent.EventID(), "") @@ -2306,7 +2309,7 @@ func TestPartialStateJoin(t *testing.T) { deviceListUpdateChannel1 chan gomatrixserverlib.DeviceListUpdateEvent, deviceListUpdateChannel2 chan gomatrixserverlib.DeviceListUpdateEvent, room *federation.ServerRoom, - ) (syncToken string, psjResult partialStateJoinResult) { + ) (syncToken string, server2Room *federation.ServerRoom, psjResult partialStateJoinResult) { derek := server1.UserID("derek") elsie := server2.UserID("elsie") @@ -2331,7 +2334,7 @@ func TestPartialStateJoin(t *testing.T) { psjResult = beginPartialStateJoin(t, server1, room, alice) // @elsie:server2 joins the room. - server2.MustJoinRoom( + server2Room = server2.MustJoinRoom( t, deployment, gomatrixserverlib.ServerName(server1.ServerName()), @@ -2367,7 +2370,7 @@ func TestPartialStateJoin(t *testing.T) { server1.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{badKickEvent.JSON()}, nil) awaitEventViaSync(t, alice, room.RoomID, badKickEvent.EventID(), syncToken) - return syncToken, psjResult + return syncToken, server2Room, psjResult } // setupAnotherSharedRoomThenLeave has @alice:hs1 create a public room, @elsie:server2 join @@ -2377,14 +2380,14 @@ func TestPartialStateJoin(t *testing.T) { t *testing.T, deployment *docker.Deployment, alice *client.CSAPI, server1 *server, server2 *server, partialStateRoom *federation.ServerRoom, syncToken string, - ) string { + ) (nextSyncToken string, leaveSharedRoom func()) { elsie := server2.UserID("elsie") // @alice:hs1 creates a public room. roomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"}) // @elsie:server2 joins the room. - server2.MustJoinRoom(t, deployment, "hs1", roomID, elsie) + server2Room := server2.MustJoinRoom(t, deployment, "hs1", roomID, elsie) alice.MustSyncUntil(t, client.SyncReq{ Since: syncToken, @@ -2401,7 +2404,13 @@ func TestPartialStateJoin(t *testing.T) { server1.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{leaveEvent.JSON()}, nil) syncToken = awaitEventViaSync(t, alice, partialStateRoom.RoomID, leaveEvent.EventID(), syncToken) - return syncToken + leaveSharedRoom = func() { + server2.WithWaitForLeave(t, server2Room, alice.UserID, func() { + alice.LeaveRoom(t, roomID) + }) + } + + return syncToken, leaveSharedRoom } // testMissedDeviceListUpdateSentOncePartialJoinCompletes takes a room where hs1 incorrectly @@ -2427,7 +2436,8 @@ func TestPartialStateJoin(t *testing.T) { // The homeserver under test cannot simply use the current state of the room to // determine which device list updates it must send out once the partial state join // completes. - setupAnotherSharedRoomThenLeave(t, deployment, alice, server1, server2, room, syncToken) + _, leaveSharedRoom := setupAnotherSharedRoomThenLeave(t, deployment, alice, server1, server2, room, syncToken) + defer leaveSharedRoom() } // Finish the partial state join. @@ -2454,8 +2464,8 @@ func TestPartialStateJoin(t *testing.T) { // The room starts with @charlie:server1 and @derek:server1 in it. // @t26alice:hs1 joins the room, followed by @elsie:server2. // @elsie:server2 is kicked with an invalid event. - syncToken, psjResult := setupIncorrectlyAcceptedKick(t, deployment, alice, server1, server2, deviceListUpdateChannel1, deviceListUpdateChannel2, room) - defer psjResult.Destroy(t) + syncToken, server2Room, psjResult := setupIncorrectlyAcceptedKick(t, deployment, alice, server1, server2, deviceListUpdateChannel1, deviceListUpdateChannel2, room) + defer server2.WithWaitForLeave(t, server2Room, alice.UserID, func() { psjResult.Destroy(t) }) // @t26alice:hs1 sends out a device list update which is missed by @elsie:server2. // @elsie:server2 must receive missed device list updates once the partial state join finishes. @@ -2475,7 +2485,7 @@ func TestPartialStateJoin(t *testing.T) { // The room starts with @charlie:server1 and @derek:server1 in it. // @t27alice:hs1 joins the room, followed by @elsie:server2. // @elsie:server2 is kicked with an invalid event. - syncToken, psjResult := setupIncorrectlyAcceptedKick(t, deployment, alice, server1, server2, deviceListUpdateChannel1, deviceListUpdateChannel2, room) + syncToken, _, psjResult := setupIncorrectlyAcceptedKick(t, deployment, alice, server1, server2, deviceListUpdateChannel1, deviceListUpdateChannel2, room) defer psjResult.Destroy(t) // @t27alice:hs1 sends out a device list update which is missed by @elsie:server2. @@ -2506,7 +2516,7 @@ func TestPartialStateJoin(t *testing.T) { // The room starts with @charlie:server1 and @derek:server1 in it. // @elsie:server2 joins the room, followed by @t28alice:hs1. // server1 does not tell hs1 that server2 is in the room. - server2.MustJoinRoom( + server2Room := server2.MustJoinRoom( t, deployment, gomatrixserverlib.ServerName(server1.ServerName()), @@ -2515,7 +2525,7 @@ func TestPartialStateJoin(t *testing.T) { true, ) psjResult := beginPartialStateJoin(t, server1, room, alice) - defer psjResult.Destroy(t) + defer server2.WithWaitForLeave(t, server2Room, alice.UserID, func() { psjResult.Destroy(t) }) // @t28alice:hs1 sends out a device list update which is missed by @elsie:server2. // @elsie:server2 must receive missed device list updates once the partial state join finishes. @@ -2907,7 +2917,7 @@ func TestPartialStateJoin(t *testing.T) { // @charlie joins the room. // Now @charlie's device list is definitely being tracked. - server.MustJoinRoom(t, deployment, "hs1", otherRoomID, server.UserID("charlie")) + otherRoom := server.MustJoinRoom(t, deployment, "hs1", otherRoomID, server.UserID("charlie")) alice.MustSyncUntil(t, client.SyncReq{ Since: syncToken, @@ -2915,6 +2925,7 @@ func TestPartialStateJoin(t *testing.T) { }, client.SyncJoinedTo(server.UserID("charlie"), otherRoomID), ) + defer server.WithWaitForLeave(t, otherRoom, alice.UserID, func() { alice.LeaveRoom(t, otherRoomID) }) // Depending on the homeserver implementation, @t31alice:hs1 must have been told that either: // * charlie updated their device list, or