diff --git a/.github/workflows/single_sdk_tests.yml b/.github/workflows/single_sdk_tests.yml index 636a0e0..3e47997 100644 --- a/.github/workflows/single_sdk_tests.yml +++ b/.github/workflows/single_sdk_tests.yml @@ -95,6 +95,8 @@ jobs: ln -s . complement-crypto # Setup code we always need + # If you're bumping the image version here, don't forget to bump it in ./tests.yaml and the + # README as well! - name: Pull synapse service v1.115.0 and mitmproxy shell: bash run: | diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index d599e7c..80f9bb5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -71,6 +71,8 @@ jobs: ./install_uniffi_bindgen_go.sh && ./rebuild_rust_sdk.sh ./rust-sdk # Temporary: as it takes 3m to build the complement synapse image >:( + # If you're bumping the image version here, don't forget to bump it in ./single_sdk_tests.yml + # and the README as well! - name: Pull synapse service v1.115.0 and mitmproxy shell: bash run: | diff --git a/.gitignore b/.gitignore index 728c285..f944a54 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ js-sdk/dist /internal/api/js/chrome/dist /internal/api/js/js-sdk/dist/ /internal/api/js/js-sdk/node_modules/ +/internal/api/rust/matrix_sdk* /internal/tests/logs/ __pycache__ /rust-sdk/ diff --git a/README.md b/README.md index 7e9d1f3..9f0d104 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ The [version] is split into the URL and TAG|BRANCH then fed directly into 'git c ### Running -Find a complement-compatible homeserver image. If you don't care which image is used, use `ghcr.io/matrix-org/synapse-service:v1.114.0` +Find a complement-compatible homeserver image. If you don't care which image is used, use `ghcr.io/matrix-org/synapse-service:v1.115.0` which will Just Work out-of-the-box. To run only rust tests: diff --git a/internal/api/rust/rust.go b/internal/api/rust/rust.go index 3ec3e4a..6801771 100644 --- a/internal/api/rust/rust.go +++ b/internal/api/rust/rust.go @@ -52,9 +52,10 @@ const ( const ProcessNameNSE string = "NSE" type RustRoomInfo struct { - stream *matrix_sdk_ffi.TaskHandle - room *matrix_sdk_ffi.Room - timeline []*api.Event + stream *matrix_sdk_ffi.TaskHandle + room *matrix_sdk_ffi.Room + timeline []*api.Event + ui_timeline *matrix_sdk_ffi.Timeline } type RustClient struct { @@ -203,7 +204,7 @@ func (c *RustClient) Login(t ct.TestLike, opts api.ClientCreationOpts) error { if err != nil { return fmt.Errorf("Client.Login failed: %s", err) } - // let the client upload device keys and OTKs + // let the client upload device keys and one-time keys e := c.FFIClient.Encryption() e.WaitForE2eeInitializationTasks() e.Destroy() @@ -326,8 +327,8 @@ func (c *RustClient) Close(t ct.TestLike) { func (c *RustClient) GetEvent(t ct.TestLike, roomID, eventID string) (*api.Event, error) { t.Helper() - room := c.findRoom(t, roomID) - timelineItem, err := mustGetTimeline(t, room).GetEventTimelineItemByEventId(eventID) + _, ui_timeline := c.findRoom(t, roomID) + timelineItem, err := ui_timeline.GetEventTimelineItemByEventId(eventID) if err != nil { return nil, fmt.Errorf("failed to GetEventTimelineItemByEventId(%s): %s", eventID, err) } @@ -378,10 +379,10 @@ func (c *RustClient) StartSyncing(t ct.TestLike) (stopSyncing func(), err error) // track new rooms when they are made allRoomsListener := newGenericStateListener[[]matrix_sdk_ffi.RoomListEntriesUpdate]() go func() { - var allRooms DynamicSlice[*matrix_sdk_ffi.RoomListItem] + var allRooms DynamicSlice[*matrix_sdk_ffi.Room] for !allRoomsListener.isClosed.Load() { updates := <-allRoomsListener.ch - var newEntries []*matrix_sdk_ffi.RoomListItem + var newEntries []*matrix_sdk_ffi.Room for _, update := range updates { switch x := update.(type) { case matrix_sdk_ffi.RoomListEntriesUpdateAppend: @@ -464,14 +465,14 @@ func (c *RustClient) StartSyncing(t ct.TestLike) (stopSyncing func(), err error) // provide a bogus room ID. func (c *RustClient) IsRoomEncrypted(t ct.TestLike, roomID string) (bool, error) { t.Helper() - r := c.findRoom(t, roomID) + r, _ := c.findRoom(t, roomID) if r == nil { rooms := c.FFIClient.Rooms() return false, fmt.Errorf("failed to find room %s, got %d rooms", roomID, len(rooms)) } encryptionState, err := r.LatestEncryptionState() if err != nil { - err = fmt.Errorf("IsRoomEncrpted(rust): %s", err) + err = fmt.Errorf("IsRoomEncrypted(rust): %s", err) return false, err } return encryptionState == matrix_sdk_base.EncryptionStateEncrypted, nil @@ -541,7 +542,7 @@ func (c *RustClient) SendMessage(t ct.TestLike, roomID, text string) (eventID st // we need a timeline listener before we can send messages, AND that listener must be attached to the // same *Room you call .Send on :S c.ensureListening(t, roomID) - r := c.findRoom(t, roomID) + r, timeline := c.findRoom(t, roomID) cancel := c.roomsListener.AddListener(func(broadcastRoomID string) bool { if roomID != broadcastRoomID { return false @@ -573,9 +574,8 @@ func (c *RustClient) SendMessage(t ct.TestLike, roomID, text string) (eventID st err = fmt.Errorf("SendMessage(rust) %s: failed to find room %s", c.userID, roomID) return } - timeline, err := r.Timeline() - if err != nil { - err = fmt.Errorf("SendMessage(rust) %s: %s", c.userID, err) + if timeline == nil { + err = fmt.Errorf("SendMessage(rust) %s: failed to get timeline %s", c.userID, roomID) return } timeline.Send(matrix_sdk_ffi.MessageEventContentFromHtml(text, text)) @@ -590,17 +590,17 @@ func (c *RustClient) SendMessage(t ct.TestLike, roomID, text string) (eventID st func (c *RustClient) InviteUser(t ct.TestLike, roomID, userID string) error { t.Helper() - r := c.findRoom(t, roomID) + r, _ := c.findRoom(t, roomID) return r.InviteUserById(userID) } func (c *RustClient) Backpaginate(t ct.TestLike, roomID string, count int) error { t.Helper() - r := c.findRoom(t, roomID) - if r == nil { - return fmt.Errorf("Backpaginate: cannot find room %s", roomID) + _, timeline := c.findRoom(t, roomID) + if timeline == nil { + return fmt.Errorf("Backpaginate: cannot find timeline for room %s", roomID) } - _, err := mustGetTimeline(t, r).PaginateBackwards(uint16(count)) + _, err := timeline.PaginateBackwards(uint16(count)) if err != nil { return fmt.Errorf("cannot PaginateBackwards in %s: %s", roomID, err) } @@ -611,42 +611,39 @@ func (c *RustClient) UserID() string { return c.userID } -func (c *RustClient) findRoomInCache(roomID string) *matrix_sdk_ffi.Room { +func (c *RustClient) findRoomInCache(roomID string) (*matrix_sdk_ffi.Room, *matrix_sdk_ffi.Timeline) { c.roomsMu.RLock() defer c.roomsMu.RUnlock() // do we have a reference to it already? roomInfo := c.rooms[roomID] if roomInfo != nil { - return roomInfo.room + return roomInfo.room, roomInfo.ui_timeline } - return nil + return nil, nil } // findRoom tries to find the room in the FFI client. Has a cache of already found rooms to ensure // the same pointer is always returned for the same room. -func (c *RustClient) findRoom(t ct.TestLike, roomID string) *matrix_sdk_ffi.Room { +func (c *RustClient) findRoom(t ct.TestLike, roomID string) (*matrix_sdk_ffi.Room, *matrix_sdk_ffi.Timeline) { t.Helper() - room := c.findRoomInCache(roomID) + room, ui_timeline := c.findRoomInCache(roomID) if room != nil { - return room + return room, ui_timeline } // try to find it in all_rooms if c.allRooms != nil { - roomListItem, err := c.allRooms.Room(roomID) + room, err := c.allRooms.Room(roomID) if err != nil { c.Logf(t, "allRooms.Room(%s) err: %s", roomID, err) - } else if roomListItem != nil { - room, err := c.FFIClient.GetRoom(roomID) - if err != nil { - c.Logf(t, "allRooms.FullRoom(%s) err: %s", roomID, err) - } else { - c.roomsMu.Lock() - c.rooms[roomID] = &RustRoomInfo{ - room: *room, - } - c.roomsMu.Unlock() - return *room + } else if room != nil { + c.roomsMu.Lock() + ui_timeline := mustGetTimeline(t, room) + c.rooms[roomID] = &RustRoomInfo{ + room: room, + ui_timeline: ui_timeline, } + c.roomsMu.Unlock() + return room, ui_timeline } } // try to find it from FFI @@ -657,17 +654,20 @@ func (c *RustClient) findRoom(t ct.TestLike, roomID string) *matrix_sdk_ffi.Room _, exists := c.rooms[rid] if !exists { c.roomsMu.Lock() + room := rooms[i] + ui_timeline := mustGetTimeline(t, room) c.rooms[rid] = &RustRoomInfo{ - room: rooms[i], + room: room, + ui_timeline: ui_timeline, } c.roomsMu.Unlock() } if r.Id() == roomID { - return c.rooms[rid].room + return c.rooms[rid].room, c.rooms[rid].ui_timeline } } // we really don't know about this room yet - return nil + return nil, nil } func (c *RustClient) Logf(t ct.TestLike, format string, args ...interface{}) { @@ -684,14 +684,14 @@ func (c *RustClient) logToFile(t ct.TestLike, format string, args ...interface{} func (c *RustClient) ensureListening(t ct.TestLike, roomID string) { t.Helper() - r := c.findRoom(t, roomID) + r, ui_timeline := c.findRoom(t, roomID) if r == nil { // we allow the room to not exist yet. If this happens, wait until we see the room before continuing c.roomsListener.AddListener(func(broadcastRoomID string) bool { if broadcastRoomID != roomID { return false } - if room := c.findRoom(t, roomID); room != nil { + if room, _ := c.findRoom(t, roomID); room != nil { // Do this asynchronously because adding a timeline listener is done synchronously // which will cause "signal arrived during cgo execution" if it happens within // this rooms listener callback. @@ -704,7 +704,9 @@ func (c *RustClient) ensureListening(t ct.TestLike, roomID string) { }) return } + must.NotEqual(t, r, nil, fmt.Sprintf("room %s does not exist", roomID)) + must.NotEqual(t, ui_timeline, nil, fmt.Sprintf("ui_timeline for room %s does not exist", roomID)) info := c.rooms[roomID] if info != nil && info.stream != nil { @@ -718,7 +720,8 @@ func (c *RustClient) ensureListening(t ct.TestLike, roomID string) { // as setting the initial entries clears the timeline, which can then result in test flakes. waiter := helpers.NewWaiter() c.rooms[roomID].timeline = make([]*api.Event, 0) - result := mustGetTimeline(t, r).AddListener(&timelineListener{fn: func(diff []*matrix_sdk_ffi.TimelineDiff) { + + result := ui_timeline.AddListener(&timelineListener{fn: func(diff []*matrix_sdk_ffi.TimelineDiff) { defer waiter.Finish() timeline := c.rooms[roomID].timeline var newEvents []*api.Event