Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 75 additions & 31 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type CSAPI struct {
AccessToken string
BaseURL string
Client *http.Client
// how long are we willing to wait for SyncUntil.... calls
// how long are we willing to wait for SyncUntil*.... calls
SyncUntilTimeout time.Duration
// True to enable verbose logging
Debug bool
Expand Down Expand Up @@ -127,18 +127,18 @@ func (c *CSAPI) SendEventSynced(t *testing.T, roomID string, e b.Event) string {
return eventID
}

// SyncUntilTimelineHas is a wrapper around `SyncUntil`.
// SyncUntilTimelineHas is a wrapper around `SyncUntilArray`.
// It blocks and continually calls `/sync` until
// - we have joined the given room
// - we see an event in the room for which the `check` function returns True
// If the `check` function fails the test, the failing event will be automatically logged.
// Will time out after CSAPI.SyncUntilTimeout.
func (c *CSAPI) SyncUntilTimelineHas(t *testing.T, roomID string, check func(gjson.Result) bool) {
t.Helper()
c.SyncUntil(t, "", "", "rooms.join."+GjsonEscape(roomID)+".timeline.events", check)
c.SyncUntilArray(t, "", "", "rooms.join."+GjsonEscape(roomID)+".timeline.events", check)
}

// SyncUntilInvitedTo is a wrapper around SyncUntil.
// SyncUntilInvitedTo is a wrapper around SyncUntilArray.
// It blocks and continually calls `/sync` until we've been invited to the given room.
// Will time out after CSAPI.SyncUntilTimeout.
func (c *CSAPI) SyncUntilInvitedTo(t *testing.T, roomID string) {
Expand All @@ -148,38 +148,86 @@ func (c *CSAPI) SyncUntilInvitedTo(t *testing.T, roomID string) {
event.Get("content.membership").Str == "invite" &&
event.Get("state_key").Str == c.UserID
}
c.SyncUntil(t, "", "", "rooms.invite."+GjsonEscape(roomID)+".invite_state.events", check)
c.SyncUntilArray(t, "", "", "rooms.invite."+GjsonEscape(roomID)+".invite_state.events", check)
}

// SyncUntil blocks and continually calls /sync until
// SyncUntilArray blocks and continually calls /sync until
// - the response contains a particular `key`, and
// - its corresponding value is an array
// - some element in that array makes the `check` function return true.
// If the `check` function fails the test, the failing event will be automatically logged.
// If the `check` function fails the test, the failing value will be automatically logged.
// Will time out after CSAPI.SyncUntilTimeout.
func (c *CSAPI) SyncUntil(t *testing.T, since, filter, key string, check func(gjson.Result) bool) {
t.Helper()
start := time.Now()
checkCounter := 0
func (c *CSAPI) SyncUntilArray(t *testing.T, since, filter, key string, check func(gjson.Result) bool) {
var wasFailedBacking = false
var timedOutBacking = false
var checkCounterBacking = 0

var wasFailed = &wasFailedBacking
var timedOut = &timedOutBacking
var checkCounter = &checkCounterBacking

var lastElement *gjson.Result

// Print failing events in a defer() so we handle t.Fatalf in the same way as t.Errorf
var wasFailed = t.Failed()
var lastEvent *gjson.Result
timedOut := false
defer func() {
if !wasFailed && t.Failed() {
// When failing on a fatal error
if !*wasFailed && t.Failed() {
raw := ""
if lastEvent != nil {
raw = lastEvent.Raw
if lastElement != nil {
raw = lastElement.Raw
}
if !timedOut {
t.Logf("SyncUntil: failing event %s", raw)
if !*timedOut {
t.Logf("SyncUntilArray: failing element %s", raw)
}
}
}()

arrayCheck := func(result gjson.Result) bool {
if result.IsArray() {
elements := result.Array()

for i, el := range elements {
lastElement = &elements[i]
if check(el) {
return true
}
*wasFailed = t.Failed()
// Increment counter artificially for every sub-check call
// Makes syncUntilInternal report "correctly" how many checks were made
if i != 0 {
*checkCounter++
}
}
}
return false
}

c.syncUntilInternal(t, since, filter, key, wasFailed, timedOut, checkCounter, arrayCheck)
}

// SyncUntil blocks and continually calls /sync until
// - the response contains a particular `key`, and
// - the corresponding value makes the `check` function return true.
// Will time out after CSAPI.SyncUntilTimeout.
func (c *CSAPI) SyncUntil(t *testing.T, since, filter, key string, check func(gjson.Result) bool) {
var wasFailedBacking = false
var timedOutBacking = false
var checkCounterBacking = 0

c.syncUntilInternal(t, since, filter, key, &wasFailedBacking, &timedOutBacking, &checkCounterBacking, check)
}

// Internal function helping both SyncUntil and SyncUntilArray with some logging coordination upon failure.
func (c *CSAPI) syncUntilInternal(t *testing.T, since, filter, key string, wasFailed, timedOut *bool, checkCounter *int, check func(gjson.Result) bool) {
Copy link
Copy Markdown
Contributor Author

@ShadowJonathan ShadowJonathan Dec 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function signature is a bit bonkers, but essentially, most of these are used by SyncUntilArray to "feed back" the values.

Its used in its defer'd function (as t.Fatalf just stops the goroutine like a panic and runs all defers up the stack, and i wanted to separate SyncUntil and SyncUntilArray functionality.)

checkCounter is also artificially incremented in SyncUntilArray so that it matches the total amount of check calls it has done for elements in the arrays it finds.

t.Helper()
start := time.Now()

*wasFailed = t.Failed()

for {
if time.Since(start) > c.SyncUntilTimeout {
timedOut = true
t.Fatalf("SyncUntil: timed out. Called check function %d times", checkCounter)
*timedOut = true
t.Fatalf("Sync: timed out. Called check function %d times", *checkCounter)
}
query := url.Values{
"timeout": []string{"1000"},
Expand All @@ -193,17 +241,13 @@ func (c *CSAPI) SyncUntil(t *testing.T, since, filter, key string, check func(gj
res := c.MustDoFunc(t, "GET", []string{"_matrix", "client", "r0", "sync"}, WithQueries(query))
body := ParseJSON(t, res)
since = GetJSONFieldStr(t, body, "next_batch")
keyRes := gjson.GetBytes(body, key)
if keyRes.IsArray() {
events := keyRes.Array()
for i, ev := range events {
lastEvent = &events[i]
if check(ev) {
return
}
wasFailed = t.Failed()
checkCounter++
value := gjson.GetBytes(body, key)
if value.Exists() {
if check(value) {
return
}
*wasFailed = t.Failed()
*checkCounter++
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/msc2403_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func knockingBetweenTwoUsersTest(t *testing.T, roomID string, inRoomUser, knocki

// Use our sync token from earlier to carry out an incremental sync. Initial syncs may not contain room
// leave information for obvious reasons
knockingUser.SyncUntil(
knockingUser.SyncUntilArray(
t,
since,
"",
Expand Down Expand Up @@ -311,7 +311,7 @@ func knockOnRoomSynced(t *testing.T, c *client.CSAPI, roomID, reason string, ser
knockOnRoomWithStatus(t, c, roomID, reason, serverNames, 200)

// The knock should have succeeded. Block until we see the knock appear down sync
c.SyncUntil(
c.SyncUntilArray(
t,
"",
"",
Expand Down
2 changes: 1 addition & 1 deletion tests/msc2716_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ func TestImportHistoricalMessages(t *testing.T) {
// eventIDAfterHistoricalImport without any the
// historicalEventIDs/historicalStateEventIDs in between, we're probably
// safe to assume it won't sync.
alice.SyncUntil(t, since, "", "rooms.join."+client.GjsonEscape(roomID)+".timeline.events", func(r gjson.Result) bool {
alice.SyncUntilArray(t, since, "", "rooms.join."+client.GjsonEscape(roomID)+".timeline.events", func(r gjson.Result) bool {
if includes(r.Get("event_id").Str, historicalEventIDs) || includes(r.Get("event_id").Str, historicalStateEventIDs) {
t.Fatalf("We should not see the %s historical event in /sync response but it was present", r.Get("event_id").Str)
}
Expand Down