From c32d2c478d67715b0e52a5ca469149c720763700 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Thu, 12 Mar 2026 13:56:27 -0400 Subject: [PATCH 01/18] Initial MSC4429 tests --- tests/msc4429/main_test.go | 11 ++ tests/msc4429/msc4429_test.go | 207 ++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 tests/msc4429/main_test.go create mode 100644 tests/msc4429/msc4429_test.go diff --git a/tests/msc4429/main_test.go b/tests/msc4429/main_test.go new file mode 100644 index 000000000..29daa335c --- /dev/null +++ b/tests/msc4429/main_test.go @@ -0,0 +1,11 @@ +package tests + +import ( + "testing" + + "github.com/matrix-org/complement" +) + +func TestMain(m *testing.M) { + complement.TestMain(m, "msc4429") +} diff --git a/tests/msc4429/msc4429_test.go b/tests/msc4429/msc4429_test.go new file mode 100644 index 000000000..3ee6bf9d0 --- /dev/null +++ b/tests/msc4429/msc4429_test.go @@ -0,0 +1,207 @@ +package tests + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/tidwall/gjson" + + "github.com/matrix-org/complement" + "github.com/matrix-org/complement/client" + "github.com/matrix-org/complement/helpers" + "github.com/matrix-org/complement/match" + "github.com/matrix-org/complement/must" +) + +const ( + msc4429UsersStable = "users" + msc4429UsersUnstable = "org\\.matrix\\.msc4429\\.users" +) + +func TestMSC4429ProfileUpdates(t *testing.T) { + deployment := complement.Deploy(t, 1) + defer deployment.Destroy(t) + + t.Run("Initial sync includes requested profile fields and filters others", func(t *testing.T) { + alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "alice-initial"}) + bob := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "bob-initial"}) + + mustCreateSharedRoom(t, alice, bob) + + bob.MustSetDisplayName(t, "Bob Display") + mustSetProfileField(t, bob, "m.status", map[string]interface{}{ + "text": "busy", + "emoji": "🛑", + }) + + // Exclude 'displayname' + filter := mustBuildMSC4429Filter(t, []string{"m.status"}) + res, _ := alice.MustSync(t, client.SyncReq{Filter: filter}) + + update, ok := getProfileUpdate(res, bob.UserID, "m.status") + if !ok { + t.Fatalf("missing m.status profile update for %s in initial sync: %s", bob.UserID, res.Raw) + } + must.MatchGJSON(t, update, match.JSONKeyEqual("", map[string]interface{}{ + "text": "busy", + "emoji": "🛑", + })) + assertNoProfileUpdate(t, res, bob.UserID, "displayname") + }) + + t.Run("No updates without profile_fields filter", func(t *testing.T) { + alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "alice-nofilter"}) + bob := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "bob-nofilter"}) + + mustCreateSharedRoom(t, alice, bob) + + // No filter = no profile fields returned. + _, since := alice.MustSync(t, client.SyncReq{}) + mustSetProfileField(t, bob, "m.status", map[string]interface{}{ + "text": "away", + }) + + res, _ := alice.MustSync(t, client.SyncReq{Since: since}) + assertNoProfileUpdate(t, res, bob.UserID, "m.status") + }) + + t.Run("Incremental sync returns the latest update", func(t *testing.T) { + alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "alice-latest"}) + bob := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "bob-latest"}) + + mustCreateSharedRoom(t, alice, bob) + + filter := mustBuildMSC4429Filter(t, []string{"m.status"}) + _, since := alice.MustSync(t, client.SyncReq{Filter: filter}) + + mustSetProfileField(t, bob, "m.status", map[string]interface{}{ + "text": "first", + }) + mustSetProfileField(t, bob, "m.status", map[string]interface{}{ + "text": "second", + }) + + alice.MustSyncUntil( + t, + client.SyncReq{Since: since, Filter: filter}, + syncHasProfileUpdate(bob.UserID, "m.status", map[string]interface{}{ + "text": "second", + }), + ) + }) + + t.Run("Cleared profile field is returned as null", func(t *testing.T) { + alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "alice-clear"}) + bob := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "bob-clear"}) + + mustCreateSharedRoom(t, alice, bob) + + filter := mustBuildMSC4429Filter(t, []string{"m.status"}) + _, since := alice.MustSync(t, client.SyncReq{Filter: filter}) + + mustSetProfileField(t, bob, "m.status", map[string]interface{}{ + "text": "busy", + }) + since = alice.MustSyncUntil( + t, + client.SyncReq{Since: since, Filter: filter}, + syncHasProfileUpdate(bob.UserID, "m.status", map[string]interface{}{ + "text": "busy", + }), + ) + + mustSetProfileField(t, bob, "m.status", nil) + alice.MustSyncUntil( + t, + client.SyncReq{Since: since, Filter: filter}, + syncHasProfileUpdate(bob.UserID, "m.status", nil), + ) + }) +} + +// mustBuildMSC4429Filter builds a filter that can be used to limit the field +// IDs returned in a `/sync` response. +func mustBuildMSC4429Filter(t *testing.T, ids []string) string { + t.Helper() + filter := map[string]interface{}{ + "profile_fields": map[string]interface{}{ + "ids": ids, + }, + "org.matrix.msc4429.profile_fields": map[string]interface{}{ + "ids": ids, + }, + } + encoded, err := json.Marshal(filter) + if err != nil { + t.Fatalf("failed to marshal MSC4429 filter: %s", err) + } + return string(encoded) +} + +// mustCreateSharedRoom creates a shared room between `alice` and `bob` and returns the +// room ID. +func mustCreateSharedRoom(t *testing.T, alice *client.CSAPI, bob *client.CSAPI) string { + t.Helper() + roomID := alice.MustCreateRoom(t, map[string]interface{}{ + "preset": "public_chat", + }) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) + bob.MustJoinRoom(t, roomID, nil) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID)) + bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID)) + return roomID +} + +// mustSetProfileField sets the given profile field ID to the given value on the given user's +// profile. +func mustSetProfileField(t *testing.T, user *client.CSAPI, field string, value interface{}) { + t.Helper() + user.MustDo(t, "PUT", []string{"_matrix", "client", "v3", "profile", user.UserID, field}, + client.WithJSONBody(t, map[string]interface{}{ + field: value, + }), + ) +} + +// getProfileUpdate extracts the given profile updates for a given user by field +// ID from a legacy `/sync` response. +func getProfileUpdate(res gjson.Result, userID, field string) (gjson.Result, bool) { + stablePath := msc4429UsersStable + "." + client.GjsonEscape(userID) + ".profile_updates." + client.GjsonEscape(field) + stableRes := res.Get(stablePath) + if stableRes.Exists() { + return stableRes, true + } + unstablePath := msc4429UsersUnstable + "." + client.GjsonEscape(userID) + ".profile_updates." + client.GjsonEscape(field) + unstableRes := res.Get(unstablePath) + if unstableRes.Exists() { + return unstableRes, true + } + return gjson.Result{}, false +} + +func assertNoProfileUpdate(t *testing.T, res gjson.Result, userID, field string) { + t.Helper() + if update, ok := getProfileUpdate(res, userID, field); ok { + t.Fatalf("unexpected profile update for %s %s: %s", userID, field, update.Raw) + } +} + +func syncHasProfileUpdate(userID, field string, expected interface{}) client.SyncCheckOpt { + return func(clientUserID string, topLevelSyncJSON gjson.Result) error { + update, ok := getProfileUpdate(topLevelSyncJSON, userID, field) + if !ok { + return fmt.Errorf("missing profile update for %s %s", userID, field) + } + if expected == nil { + if update.Type != gjson.Null { + return fmt.Errorf("expected null profile update for %s %s, got %s", userID, field, update.Type) + } + return nil + } + if err := match.JSONKeyEqual("", expected)(update); err != nil { + return fmt.Errorf("profile update mismatch for %s %s: %w", userID, field, err) + } + return nil + } +} From 58fbe50935525d346d635d98e156cfdac2e08379 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Tue, 23 Jun 2026 10:01:59 +0100 Subject: [PATCH 02/18] Comment out stable prefix support for now --- tests/msc4429/msc4429_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/msc4429/msc4429_test.go b/tests/msc4429/msc4429_test.go index 3ee6bf9d0..f7cb9ce74 100644 --- a/tests/msc4429/msc4429_test.go +++ b/tests/msc4429/msc4429_test.go @@ -15,7 +15,8 @@ import ( ) const ( - msc4429UsersStable = "users" + // TODO: Support stable prefix once MSC4429 is accepted. + // msc4429UsersStable = "users" msc4429UsersUnstable = "org\\.matrix\\.msc4429\\.users" ) @@ -167,11 +168,6 @@ func mustSetProfileField(t *testing.T, user *client.CSAPI, field string, value i // getProfileUpdate extracts the given profile updates for a given user by field // ID from a legacy `/sync` response. func getProfileUpdate(res gjson.Result, userID, field string) (gjson.Result, bool) { - stablePath := msc4429UsersStable + "." + client.GjsonEscape(userID) + ".profile_updates." + client.GjsonEscape(field) - stableRes := res.Get(stablePath) - if stableRes.Exists() { - return stableRes, true - } unstablePath := msc4429UsersUnstable + "." + client.GjsonEscape(userID) + ".profile_updates." + client.GjsonEscape(field) unstableRes := res.Get(unstablePath) if unstableRes.Exists() { From 543b1135486800cc0524c70ed2bb2d0acc189904 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Tue, 23 Jun 2026 10:56:50 +0100 Subject: [PATCH 03/18] Use MustSyncUntil in initial sync test Add various comments throughout for clarification. --- tests/msc4429/msc4429_test.go | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/tests/msc4429/msc4429_test.go b/tests/msc4429/msc4429_test.go index f7cb9ce74..9336ca143 100644 --- a/tests/msc4429/msc4429_test.go +++ b/tests/msc4429/msc4429_test.go @@ -11,7 +11,6 @@ import ( "github.com/matrix-org/complement/client" "github.com/matrix-org/complement/helpers" "github.com/matrix-org/complement/match" - "github.com/matrix-org/complement/must" ) const ( @@ -30,27 +29,30 @@ func TestMSC4429ProfileUpdates(t *testing.T) { mustCreateSharedRoom(t, alice, bob) + // Bob sets their displayname. bob.MustSetDisplayName(t, "Bob Display") + // Bob sets their status. mustSetProfileField(t, bob, "m.status", map[string]interface{}{ "text": "busy", "emoji": "🛑", }) - // Exclude 'displayname' + // Alice /sync's, but only asks for "m.status" changes. + // Exclude 'displayname'. filter := mustBuildMSC4429Filter(t, []string{"m.status"}) res, _ := alice.MustSync(t, client.SyncReq{Filter: filter}) - update, ok := getProfileUpdate(res, bob.UserID, "m.status") - if !ok { - t.Fatalf("missing m.status profile update for %s in initial sync: %s", bob.UserID, res.Raw) - } - must.MatchGJSON(t, update, match.JSONKeyEqual("", map[string]interface{}{ + // We should see the m.status profile update. + alice.MustSyncUntil(t, client.SyncReq{Filter: filter}, syncHasProfileUpdate(alice.UserID, "m.status", map[string]interface{}{ "text": "busy", "emoji": "🛑", })) + + // We should NOT see a displayname profile update. assertNoProfileUpdate(t, res, bob.UserID, "displayname") }) + // Receiving profile updates are an opt-in mechanism, according to MSC4429. t.Run("No updates without profile_fields filter", func(t *testing.T) { alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "alice-nofilter"}) bob := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "bob-nofilter"}) @@ -59,14 +61,18 @@ func TestMSC4429ProfileUpdates(t *testing.T) { // No filter = no profile fields returned. _, since := alice.MustSync(t, client.SyncReq{}) + + // Bob sets their status. mustSetProfileField(t, bob, "m.status", map[string]interface{}{ "text": "away", }) + // Assert that alice does not receive it. res, _ := alice.MustSync(t, client.SyncReq{Since: since}) assertNoProfileUpdate(t, res, bob.UserID, "m.status") }) + // Check that only the latest update is returned per-user per-field. t.Run("Incremental sync returns the latest update", func(t *testing.T) { alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "alice-latest"}) bob := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "bob-latest"}) @@ -92,6 +98,7 @@ func TestMSC4429ProfileUpdates(t *testing.T) { ) }) + // Test that the homeserver informs the client when a profile field is cleared. t.Run("Cleared profile field is returned as null", func(t *testing.T) { alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "alice-clear"}) bob := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "bob-clear"}) @@ -101,9 +108,12 @@ func TestMSC4429ProfileUpdates(t *testing.T) { filter := mustBuildMSC4429Filter(t, []string{"m.status"}) _, since := alice.MustSync(t, client.SyncReq{Filter: filter}) + // Bob sets a status. mustSetProfileField(t, bob, "m.status", map[string]interface{}{ "text": "busy", }) + + // Wait until alice can see the status. since = alice.MustSyncUntil( t, client.SyncReq{Since: since, Filter: filter}, @@ -112,7 +122,10 @@ func TestMSC4429ProfileUpdates(t *testing.T) { }), ) + // Bob clears their status. mustSetProfileField(t, bob, "m.status", nil) + + // Wait until alice sees the status be set to `null` (nil). alice.MustSyncUntil( t, client.SyncReq{Since: since, Filter: filter}, @@ -176,6 +189,8 @@ func getProfileUpdate(res gjson.Result, userID, field string) (gjson.Result, boo return gjson.Result{}, false } +// assertNoProfileUpdate asserts that a user has not updated a field of their +// profile in the given legacy /sync response JSON. func assertNoProfileUpdate(t *testing.T, res gjson.Result, userID, field string) { t.Helper() if update, ok := getProfileUpdate(res, userID, field); ok { @@ -183,6 +198,8 @@ func assertNoProfileUpdate(t *testing.T, res gjson.Result, userID, field string) } } +// syncHasProfileUpdate checks whether a given sync response contains a profile +// update of the given, expected field and value. func syncHasProfileUpdate(userID, field string, expected interface{}) client.SyncCheckOpt { return func(clientUserID string, topLevelSyncJSON gjson.Result) error { update, ok := getProfileUpdate(topLevelSyncJSON, userID, field) From 2a89f7d16861089b77ee12d6d4cbb363f254e25a Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Tue, 23 Jun 2026 11:02:33 +0100 Subject: [PATCH 04/18] Clarify why we perform an initial sync --- tests/msc4429/msc4429_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/msc4429/msc4429_test.go b/tests/msc4429/msc4429_test.go index 9336ca143..556454557 100644 --- a/tests/msc4429/msc4429_test.go +++ b/tests/msc4429/msc4429_test.go @@ -59,7 +59,7 @@ func TestMSC4429ProfileUpdates(t *testing.T) { mustCreateSharedRoom(t, alice, bob) - // No filter = no profile fields returned. + // Perform an initial sync to get a since token. _, since := alice.MustSync(t, client.SyncReq{}) // Bob sets their status. @@ -67,7 +67,7 @@ func TestMSC4429ProfileUpdates(t *testing.T) { "text": "away", }) - // Assert that alice does not receive it. + // Assert that alice does not receive it in an incremental sync. res, _ := alice.MustSync(t, client.SyncReq{Since: since}) assertNoProfileUpdate(t, res, bob.UserID, "m.status") }) From 5ea104f969429316dfdb5fe7a52e1cc7e69978b7 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Tue, 23 Jun 2026 11:23:16 +0100 Subject: [PATCH 05/18] Assert third user can see status update --- tests/msc4429/msc4429_test.go | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/tests/msc4429/msc4429_test.go b/tests/msc4429/msc4429_test.go index 556454557..68a4940ca 100644 --- a/tests/msc4429/msc4429_test.go +++ b/tests/msc4429/msc4429_test.go @@ -56,8 +56,9 @@ func TestMSC4429ProfileUpdates(t *testing.T) { t.Run("No updates without profile_fields filter", func(t *testing.T) { alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "alice-nofilter"}) bob := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "bob-nofilter"}) + charlie := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "charlie-nofilter"}) - mustCreateSharedRoom(t, alice, bob) + mustCreateSharedRoom(t, alice, bob, charlie) // Perform an initial sync to get a since token. _, since := alice.MustSync(t, client.SyncReq{}) @@ -65,9 +66,18 @@ func TestMSC4429ProfileUpdates(t *testing.T) { // Bob sets their status. mustSetProfileField(t, bob, "m.status", map[string]interface{}{ "text": "away", + "emoji": "🟡", }) - // Assert that alice does not receive it in an incremental sync. + // Assert that charlie receives bob's profile update in an incremental sync + // with the appropriate filter set. + filter := mustBuildMSC4429Filter(t, []string{"m.status"}) + charlie.MustSyncUntil(t, client.SyncReq{Filter: filter}, syncHasProfileUpdate(bob.UserID, "m.status", map[string]interface{}{ + "text": "away", + "emoji": "🟡", + })) + + // Assert that alice does not receive the profile update in an incremental sync. res, _ := alice.MustSync(t, client.SyncReq{Since: since}) assertNoProfileUpdate(t, res, bob.UserID, "m.status") }) @@ -155,15 +165,20 @@ func mustBuildMSC4429Filter(t *testing.T, ids []string) string { // mustCreateSharedRoom creates a shared room between `alice` and `bob` and returns the // room ID. -func mustCreateSharedRoom(t *testing.T, alice *client.CSAPI, bob *client.CSAPI) string { +func mustCreateSharedRoom(t *testing.T, users ...*client.CSAPI) string { t.Helper() - roomID := alice.MustCreateRoom(t, map[string]interface{}{ + + // Use one of the users to create the room. + roomID := users[0].MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) - alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) - bob.MustJoinRoom(t, roomID, nil) - alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID)) - bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID)) + + // Join all of the given users to the room. + for _, user := range users { + user.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(user.UserID, roomID)) + user.MustJoinRoom(t, roomID, nil) + } + return roomID } From a1934aca56123b3e94a8c1ca710f6a4b17d4a510 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Tue, 23 Jun 2026 11:39:29 +0100 Subject: [PATCH 06/18] Clarify assertNoprofileUpdate error message --- tests/msc4429/msc4429_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/msc4429/msc4429_test.go b/tests/msc4429/msc4429_test.go index 68a4940ca..008176903 100644 --- a/tests/msc4429/msc4429_test.go +++ b/tests/msc4429/msc4429_test.go @@ -209,7 +209,7 @@ func getProfileUpdate(res gjson.Result, userID, field string) (gjson.Result, boo func assertNoProfileUpdate(t *testing.T, res gjson.Result, userID, field string) { t.Helper() if update, ok := getProfileUpdate(res, userID, field); ok { - t.Fatalf("unexpected profile update for %s %s: %s", userID, field, update.Raw) + t.Fatalf("expected no profile update for %s %s: %s", userID, field, update.Raw) } } From 4cddfeab619504b8ed4a9c86df3892953cb63c75 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Tue, 23 Jun 2026 14:34:46 +0100 Subject: [PATCH 07/18] Add test for receiving `null` profile update when users no longer share a room --- tests/msc4429/msc4429_test.go | 72 ++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/tests/msc4429/msc4429_test.go b/tests/msc4429/msc4429_test.go index 008176903..857299fbb 100644 --- a/tests/msc4429/msc4429_test.go +++ b/tests/msc4429/msc4429_test.go @@ -65,7 +65,7 @@ func TestMSC4429ProfileUpdates(t *testing.T) { // Bob sets their status. mustSetProfileField(t, bob, "m.status", map[string]interface{}{ - "text": "away", + "text": "away", "emoji": "🟡", }) @@ -142,6 +142,50 @@ func TestMSC4429ProfileUpdates(t *testing.T) { syncHasProfileUpdate(bob.UserID, "m.status", nil), ) }) + + t.Run("A user leaving the last shared room returns a profile update of null", func(t *testing.T) { + alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "alice-leave"}) + bob := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "bob-leave"}) + + roomID := mustCreateSharedRoom(t, alice, bob) + + filter := mustBuildMSC4429Filter(t, []string{"m.status"}) + since := alice.MustSyncUntil( + t, + client.SyncReq{Filter: filter}, + client.SyncJoinedTo(alice.UserID, roomID), + ) + + // Bob sets a status while Alice and Bob share a room. + mustSetProfileField(t, bob, "m.status", map[string]interface{}{ + "text": "busy", + }) + + // Alice receives Bob's changed profile field. + since = alice.MustSyncUntil( + t, + client.SyncReq{Since: since, Filter: filter}, + syncHasProfileUpdate(bob.UserID, "m.status", map[string]interface{}{ + "text": "busy", + }), + ) + + // Bob leaves the only room shared with Alice. + bob.MustLeaveRoom(t, roomID) + + // Alice receives a null profile_updates value for Bob. This tells + // clients to clear their local cache for Bob's profile. + alice.MustSyncUntil( + t, + client.SyncReq{Since: since, Filter: filter}, + // Check that bob left the room and we get a null profile field for them. + // + // `MustSyncUntil` will loop until both checks are true. That + // doesn't necessarily have to happen in the same sync response. + client.SyncLeftFrom(bob.UserID, roomID), + syncHasProfileUpdatesNull(bob.UserID), + ) + }) } // mustBuildMSC4429Filter builds a filter that can be used to limit the field @@ -204,6 +248,17 @@ func getProfileUpdate(res gjson.Result, userID, field string) (gjson.Result, boo return gjson.Result{}, false } +// getProfileUpdates extracts all profile updates for a given user from a legacy +// `/sync` response. +func getProfileUpdates(res gjson.Result, userID string) (gjson.Result, bool) { + unstablePath := msc4429UsersUnstable + "." + client.GjsonEscape(userID) + ".profile_updates" + unstableRes := res.Get(unstablePath) + if unstableRes.Exists() { + return unstableRes, true + } + return gjson.Result{}, false +} + // assertNoProfileUpdate asserts that a user has not updated a field of their // profile in the given legacy /sync response JSON. func assertNoProfileUpdate(t *testing.T, res gjson.Result, userID, field string) { @@ -233,3 +288,18 @@ func syncHasProfileUpdate(userID, field string, expected interface{}) client.Syn return nil } } + +// syncHasProfileUpdatesNull checks whether a sync response contains a null +// profile_updates value for the given user. +func syncHasProfileUpdatesNull(userID string) client.SyncCheckOpt { + return func(clientUserID string, topLevelSyncJSON gjson.Result) error { + updates, ok := getProfileUpdates(topLevelSyncJSON, userID) + if !ok { + return fmt.Errorf("missing profile updates for %s", userID) + } + if updates.Type != gjson.Null { + return fmt.Errorf("expected a null profile update for %s, got %s", userID, updates.Type) + } + return nil + } +} From 2b67137ea2b3c495584c231b6313c7389ec6b4b8 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Tue, 23 Jun 2026 15:10:28 +0100 Subject: [PATCH 08/18] Add MSC4429 tests to Synapse in CI --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 528d0f21c..210bf5e33 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,7 +42,7 @@ jobs: - homeserver: Synapse repo: element-hq/synapse tags: synapse_blacklist - packages: ./tests/msc3874 ./tests/msc3902 ./tests/msc4306 + packages: ./tests/msc3874 ./tests/msc3902 ./tests/msc4306 ./tests/msc4429 env: "COMPLEMENT_ENABLE_DIRTY_RUNS=1 COMPLEMENT_SHARE_ENV_PREFIX=PASS_ PASS_SYNAPSE_COMPLEMENT_DATABASE=sqlite" timeout: 20m From 3b4d4f7cd677497f7d3de3edc3a80fd9b85aa05b Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Wed, 24 Jun 2026 09:31:47 +0100 Subject: [PATCH 09/18] Fix ordering of joining vs syncing --- tests/msc4429/msc4429_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/msc4429/msc4429_test.go b/tests/msc4429/msc4429_test.go index 857299fbb..0d7de2f3b 100644 --- a/tests/msc4429/msc4429_test.go +++ b/tests/msc4429/msc4429_test.go @@ -219,8 +219,8 @@ func mustCreateSharedRoom(t *testing.T, users ...*client.CSAPI) string { // Join all of the given users to the room. for _, user := range users { - user.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(user.UserID, roomID)) user.MustJoinRoom(t, roomID, nil) + user.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(user.UserID, roomID)) } return roomID From bf3cbef54ac68e6c748a25eb41aef08ff86996c6 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Wed, 24 Jun 2026 09:33:00 +0100 Subject: [PATCH 10/18] alice <-> bob `m.status` checking --- tests/msc4429/msc4429_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/msc4429/msc4429_test.go b/tests/msc4429/msc4429_test.go index 0d7de2f3b..d3aa78eb3 100644 --- a/tests/msc4429/msc4429_test.go +++ b/tests/msc4429/msc4429_test.go @@ -43,7 +43,7 @@ func TestMSC4429ProfileUpdates(t *testing.T) { res, _ := alice.MustSync(t, client.SyncReq{Filter: filter}) // We should see the m.status profile update. - alice.MustSyncUntil(t, client.SyncReq{Filter: filter}, syncHasProfileUpdate(alice.UserID, "m.status", map[string]interface{}{ + alice.MustSyncUntil(t, client.SyncReq{Filter: filter}, syncHasProfileUpdate(bob.UserID, "m.status", map[string]interface{}{ "text": "busy", "emoji": "🛑", })) From 2f701517a4b69a97df377767c938af61abacb0df Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Wed, 24 Jun 2026 09:35:34 +0100 Subject: [PATCH 11/18] Make an incremental sync to get charlie's status update --- tests/msc4429/msc4429_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/msc4429/msc4429_test.go b/tests/msc4429/msc4429_test.go index d3aa78eb3..613e4fa8c 100644 --- a/tests/msc4429/msc4429_test.go +++ b/tests/msc4429/msc4429_test.go @@ -72,7 +72,7 @@ func TestMSC4429ProfileUpdates(t *testing.T) { // Assert that charlie receives bob's profile update in an incremental sync // with the appropriate filter set. filter := mustBuildMSC4429Filter(t, []string{"m.status"}) - charlie.MustSyncUntil(t, client.SyncReq{Filter: filter}, syncHasProfileUpdate(bob.UserID, "m.status", map[string]interface{}{ + charlie.MustSyncUntil(t, client.SyncReq{Filter: filter, Since: since}, syncHasProfileUpdate(bob.UserID, "m.status", map[string]interface{}{ "text": "away", "emoji": "🟡", })) From 897e7ab59d3911942cfb5c8648328a0105932dd0 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Wed, 24 Jun 2026 11:56:41 +0100 Subject: [PATCH 12/18] Add test to check that widening the sync filter does not return old updates To test that caching functionality in the homeserver does not result in old profile updates being sent down erroneously. --- tests/msc4429/msc4429_test.go | 60 +++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/msc4429/msc4429_test.go b/tests/msc4429/msc4429_test.go index 613e4fa8c..8cd318ce1 100644 --- a/tests/msc4429/msc4429_test.go +++ b/tests/msc4429/msc4429_test.go @@ -82,6 +82,45 @@ func TestMSC4429ProfileUpdates(t *testing.T) { assertNoProfileUpdate(t, res, bob.UserID, "m.status") }) + t.Run("Widening profile_fields filter does not return old filtered updates", func(t *testing.T) { + alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "alice-widen"}) + bob := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "bob-widen"}) + + mustCreateSharedRoom(t, alice, bob) + + madeUpProfileField := "complement.made-up-profile-field" + + // Get a sync token for alice. + _, since := alice.MustSync(t, client.SyncReq{}) + + // Bob updates their profile with two fields. + mustSetProfileField(t, bob, madeUpProfileField, "foo") + mustSetProfileField(t, bob, "m.status", map[string]interface{}{ + "text": "away", + }) + + // Alice advances their sync token requesting just one of the profile fields. + // Widening the filter later must not make this old status update appear. + filter := mustBuildMSC4429Filter(t, []string{madeUpProfileField}) + // We should have only received the `complement.made-up-profile-field` field. + since = alice.MustSyncUntil( + t, + client.SyncReq{Since: since, Filter: filter}, + syncHasProfileUpdateWithoutFields(bob.UserID, madeUpProfileField, "foo", "m.status"), + ) + + // Bob updates a third profile field. + bob.MustSetDisplayName(t, "Bob Widened") + + // Alice should only receive the `displayname` update.` + filter = mustBuildMSC4429Filter(t, []string{madeUpProfileField, "m.status", "displayname"}) + alice.MustSyncUntil( + t, + client.SyncReq{Since: since, Filter: filter}, + syncHasProfileUpdateWithoutFields(bob.UserID, "displayname", "Bob Widened", "m.status", madeUpProfileField), + ) + }) + // Check that only the latest update is returned per-user per-field. t.Run("Incremental sync returns the latest update", func(t *testing.T) { alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "alice-latest"}) @@ -289,6 +328,27 @@ func syncHasProfileUpdate(userID, field string, expected interface{}) client.Syn } } +// syncHasProfileUpdateWithoutField checks whether a sync response contains a +// profile update for one field and does not contain another field for the same +// user. +func syncHasProfileUpdateWithoutFields(userID, field string, expected interface{}, withoutFields ...string) client.SyncCheckOpt { + return func(clientUserID string, topLevelSyncJSON gjson.Result) error { + // Check that we have one field. + if err := syncHasProfileUpdate(userID, field, expected)(clientUserID, topLevelSyncJSON); err != nil { + return err + } + + // But not the `withoutField` fields. + for _, fieldName := range withoutFields { + if update, ok := getProfileUpdate(topLevelSyncJSON, userID, fieldName); ok { + return fmt.Errorf("unexpected profile update for %s %s: %s", userID, fieldName, update.Raw) + } + } + + return nil + } +} + // syncHasProfileUpdatesNull checks whether a sync response contains a null // profile_updates value for the given user. func syncHasProfileUpdatesNull(userID string) client.SyncCheckOpt { From c466460957697faa4ec36ce1b37921cef238fb91 Mon Sep 17 00:00:00 2001 From: timedout Date: Fri, 31 Jul 2026 15:25:10 +0100 Subject: [PATCH 13/18] Don't run `TestRoomSummaryAllowedRoomIDs` against Dendrite (#908) Signed-off-by: timedout --- tests/room_summary_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/room_summary_test.go b/tests/room_summary_test.go index 31d87f470..40dbd19fa 100644 --- a/tests/room_summary_test.go +++ b/tests/room_summary_test.go @@ -1,3 +1,5 @@ +//go:build !dendrite_blacklist + // Tests the GET /_matrix/client/v1/room_summary/{roomIdOrAlias} endpoint // as specified in https://spec.matrix.org/v1.15/client-server-api/#get_matrixclientv1room_summaryroomidoralias From 6f2f66133c166f9734651b506daf041a944614fe Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Sun, 2 Aug 2026 22:35:50 -0400 Subject: [PATCH 14/18] Migrate docker module to moby Migrate from the deprecated github.com/docker/docker module to the current latest version of github.com/moby/moby, and make all changes necessary to adapt to its refactoring. --- CONTRIBUTING.md | 2 +- cmd/perftest/snapshot.go | 2 +- go.mod | 21 ++--- go.sum | 72 ++++------------ internal/docker/builder.go | 135 ++++++++++++++++-------------- internal/docker/deployer.go | 160 +++++++++++++++++++----------------- internal/docker/labels.go | 6 +- runtime/hs.go | 9 +- runtime/hs_dendrite.go | 5 +- 9 files changed, 193 insertions(+), 219 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0e5ea0e51..6c9dc358d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ We ask that everybody who contributes to this project signs off their contributi We follow a simple 'inbound=outbound' model for contributions: the act of submitting an 'inbound' contribution means that the contributor agrees to license their contribution under the same terms as the project's overall 'outbound' license - in our case, this is Apache Software License v2 (see [LICENSE](./LICENSE)). -In order to have a concrete record that your contribution is intentional and you agree to license it under the same terms as the project's license, we've adopted the same lightweight approach used by the [Linux Kernel](https://www.kernel.org/doc/html/latest/process/submitting-patches.html), [Docker](https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other projects: the [Developer Certificate of Origin](https://developercertificate.org/) (DCO). This is a simple declaration that you wrote the contribution or otherwise have the right to contribute it to Matrix: +In order to have a concrete record that your contribution is intentional and you agree to license it under the same terms as the project's license, we've adopted the same lightweight approach used by the [Linux Kernel](https://www.kernel.org/doc/html/latest/process/submitting-patches.html), [Docker](https://github.com/moby/moby/blob/master/CONTRIBUTING.md), and many other projects: the [Developer Certificate of Origin](https://developercertificate.org/) (DCO). This is a simple declaration that you wrote the contribution or otherwise have the right to contribute it to Matrix: ``` Developer Certificate of Origin diff --git a/cmd/perftest/snapshot.go b/cmd/perftest/snapshot.go index 3294ee068..ef9b28920 100644 --- a/cmd/perftest/snapshot.go +++ b/cmd/perftest/snapshot.go @@ -5,8 +5,8 @@ import ( "encoding/json" "time" - "github.com/docker/docker/api/types/container" "github.com/matrix-org/complement/internal/docker" + "github.com/moby/moby/api/types/container" ) type Snapshot struct { diff --git a/go.mod b/go.mod index f98df2f8d..fec5f3bcd 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module github.com/matrix-org/complement go 1.25.0 require ( - github.com/docker/docker v28.5.2+incompatible - github.com/docker/go-connections v0.7.0 github.com/gorilla/mux v1.8.1 github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 github.com/matrix-org/gomatrixserverlib v0.0.0-20260506075950-c9c468727353 github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 + github.com/moby/moby/api v1.55.0 + github.com/moby/moby/client v0.5.1 github.com/sirupsen/logrus v1.9.4 github.com/tidwall/gjson v1.19.0 github.com/tidwall/sjson v1.2.5 @@ -22,41 +22,34 @@ require ( codeberg.org/go-latex/latex v0.2.0 // indirect codeberg.org/go-pdf/fpdf v0.11.1 // indirect git.sr.ht/~sbinet/gg v0.7.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/containerd/log v0.1.0 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/go-units v0.4.0 // indirect + github.com/docker/go-connections v0.7.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/hashicorp/go-set/v3 v3.0.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/sys/atomicwriter v0.1.0 // indirect - github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect - github.com/morikuni/aec v1.0.0 // indirect github.com/oleiade/lane/v2 v2.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.opentelemetry.io/otel v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect golang.org/x/image v0.41.0 // indirect golang.org/x/sys v0.46.0 // indirect golang.org/x/text v0.38.0 // indirect - golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect gonum.org/v1/gonum v0.17.0 // indirect - gotest.tools/v3 v3.0.3 // indirect ) diff --git a/go.sum b/go.sum index 3dd402e8a..93a3d7cd2 100644 --- a/go.sum +++ b/go.sum @@ -12,9 +12,6 @@ git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo= git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE= git.sr.ht/~sbinet/gg v0.7.0 h1:YmNf7YKd7diDMTPm86hZa1EM3pbkOyD/zzjl0LZUdNM= git.sr.ht/~sbinet/gg v0.7.0/go.mod h1:VYeli15tpMM4EvqlivlVbbyvWZlOU+EZn4XZmfBGUdM= -github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= @@ -22,27 +19,20 @@ github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= -github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= -github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= -github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -52,16 +42,12 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/go-set/v3 v3.0.0 h1:CaJBQvQCOWoftrBcDt7Nwgo0kdpmrKxar/x2o6pV9JA= @@ -77,30 +63,22 @@ github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= -github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= -github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= -github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc= +github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.5.1 h1:tYNaJno4c0HXz12y5BiqEDy0rVTYkWzI26lGvnTMiJw= +github.com/moby/moby/client v0.5.1/go.mod h1:odLstlZ6uSnfvAgVxMpvgmb8SUdd+siH2T0GBuxVAlM= github.com/oleiade/lane/v2 v2.0.0 h1:XW/ex/Inr+bPkLd3O240xrFOhUkTd4Wy176+Gv0E3Qw= github.com/oleiade/lane/v2 v2.0.0/go.mod h1:i5FBPFAYSWCgLh58UkUGCChjcCzef/MI7PlQm2TKCeg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= -github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/shoenig/test v1.11.0 h1:NoPa5GIoBwuqzIviCrnUJa+t5Xb4xi5Z+zODJnIDsEQ= github.com/shoenig/test v1.11.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -116,22 +94,18 @@ github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= -go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= -go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -144,7 +118,6 @@ golang.org/x/image v0.41.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -156,49 +129,36 @@ golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= gonum.org/v1/plot v0.17.0 h1:d0DwPVBe9jnEGqQBoZGl/P2M9WciJbG2CnV59C9QBT4= gonum.org/v1/plot v0.17.0/go.mod h1:ipt2GUN1oqzr2O7wCjLDtw1ShfIYYNBp4o0O1Ez5B3Y= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= -google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/docker/builder.go b/internal/docker/builder.go index 8b0edc99e..82145212e 100644 --- a/internal/docker/builder.go +++ b/internal/docker/builder.go @@ -17,15 +17,14 @@ import ( "context" "fmt" "log" + "net/netip" "strings" "time" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/image" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/client" - "github.com/docker/docker/pkg/stdcopy" - "github.com/docker/go-connections/nat" + "github.com/moby/moby/api/pkg/stdcopy" + "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/network" + "github.com/moby/moby/client" "github.com/matrix-org/complement/b" "github.com/matrix-org/complement/config" @@ -77,7 +76,7 @@ func (d *Builder) Cleanup() { // removeImages removes all images with `complementLabel`. func (d *Builder) removeNetworks() error { - networks, err := d.Docker.NetworkList(context.Background(), network.ListOptions{ + networks, err := d.Docker.NetworkList(context.Background(), client.NetworkListOptions{ Filters: label( complementLabel, "complement_pkg="+d.Config.PackageNamespace, @@ -86,8 +85,8 @@ func (d *Builder) removeNetworks() error { if err != nil { return err } - for _, nw := range networks { - err = d.Docker.NetworkRemove(context.Background(), nw.ID) + for _, nw := range networks.Items { + _, err = d.Docker.NetworkRemove(context.Background(), nw.ID, client.NetworkRemoveOptions{}) if err != nil { return err } @@ -97,7 +96,7 @@ func (d *Builder) removeNetworks() error { // removeImages removes all images with `complementLabel`. func (d *Builder) removeImages() error { - images, err := d.Docker.ImageList(context.Background(), image.ListOptions{ + images, err := d.Docker.ImageList(context.Background(), client.ImageListOptions{ Filters: label( complementLabel, "complement_pkg="+d.Config.PackageNamespace, @@ -106,7 +105,7 @@ func (d *Builder) removeImages() error { if err != nil { return err } - for _, img := range images { + for _, img := range images.Items { // we only clean up localhost/complement images else if someone docker pulls // an anonymous snapshot we might incorrectly nuke it :( any non-localhost // tag marks this image as safe (as images can have multiple tags) @@ -133,7 +132,7 @@ func (d *Builder) removeImages() error { d.log("Keeping image created from blueprint %s", bprintName) continue } - _, err = d.Docker.ImageRemove(context.Background(), img.ID, image.RemoveOptions{ + _, err = d.Docker.ImageRemove(context.Background(), img.ID, client.ImageRemoveOptions{ Force: true, }) if err != nil { @@ -146,7 +145,7 @@ func (d *Builder) removeImages() error { // removeContainers removes all containers with `complementLabel`. func (d *Builder) removeContainers() error { - containers, err := d.Docker.ContainerList(context.Background(), container.ListOptions{ + containers, err := d.Docker.ContainerList(context.Background(), client.ContainerListOptions{ All: true, Filters: label( complementLabel, @@ -156,8 +155,8 @@ func (d *Builder) removeContainers() error { if err != nil { return err } - for _, c := range containers { - err = d.Docker.ContainerRemove(context.Background(), c.ID, container.RemoveOptions{ + for _, c := range containers.Items { + _, err = d.Docker.ContainerRemove(context.Background(), c.ID, client.ContainerRemoveOptions{ Force: true, }) if err != nil { @@ -168,7 +167,7 @@ func (d *Builder) removeContainers() error { } func (d *Builder) ConstructBlueprintIfNotExist(bprint b.Blueprint) error { - images, err := d.Docker.ImageList(context.Background(), image.ListOptions{ + images, err := d.Docker.ImageList(context.Background(), client.ImageListOptions{ Filters: label( "complement_blueprint="+bprint.Name, "complement_pkg="+d.Config.PackageNamespace, @@ -177,7 +176,7 @@ func (d *Builder) ConstructBlueprintIfNotExist(bprint b.Blueprint) error { if err != nil { return fmt.Errorf("ConstructBlueprintIfNotExist(%s): failed to ImageList: %w", bprint.Name, err) } - if len(images) == 0 { + if len(images.Items) == 0 { err = d.ConstructBlueprint(bprint) if err != nil { return fmt.Errorf("ConstructBlueprintIfNotExist(%s): failed to ConstructBlueprint: %w", bprint.Name, err) @@ -197,12 +196,12 @@ func (d *Builder) ConstructBlueprint(bprint b.Blueprint) error { // wait a bit for images/containers to show up in 'image ls' foundImages := false - var images []image.Summary + var images client.ImageListResult var err error waitTime := 5 * time.Second startTime := time.Now() for time.Since(startTime) < waitTime { - images, err = d.Docker.ImageList(context.Background(), image.ListOptions{ + images, err = d.Docker.ImageList(context.Background(), client.ImageListOptions{ Filters: label( complementLabel, "complement_blueprint="+bprint.Name, @@ -212,7 +211,7 @@ func (d *Builder) ConstructBlueprint(bprint b.Blueprint) error { if err != nil { return err } - if len(images) < len(bprint.Homeservers) { + if len(images.Items) < len(bprint.Homeservers) { time.Sleep(100 * time.Millisecond) } else { foundImages = true @@ -226,7 +225,7 @@ func (d *Builder) ConstructBlueprint(bprint b.Blueprint) error { return fmt.Errorf("failed to find built images via ImageList: did they all build ok?") } var imgDatas []string - for _, img := range images { + for _, img := range images.Items { imgDatas = append(imgDatas, fmt.Sprintf("%s=>%v", img.ID, img.Labels)) } d.log("Constructed blueprint '%s' : %v", bprint.Name, imgDatas) @@ -252,7 +251,7 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) { // something went wrong, but we have a container which may have interesting logs printLogs(d.Docker, res.containerID, res.contextStr) } - if delErr := d.Docker.ContainerRemove(context.Background(), res.containerID, container.RemoveOptions{ + if _, delErr := d.Docker.ContainerRemove(context.Background(), res.containerID, client.ContainerRemoveOptions{ Force: true, }); delErr != nil { d.log("%s: failed to remove container which failed to deploy: %s", res.contextStr, delErr) @@ -262,19 +261,21 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) { } // kill the container defer func(r result) { - containerInfo, err := d.Docker.ContainerInspect(context.Background(), r.containerID) + containerInfo, err := d.Docker.ContainerInspect(context.Background(), r.containerID, client.ContainerInspectOptions{}) if err != nil { d.log("%s : Can't get status of %s", r.contextStr, r.containerID) return } - if !containerInfo.State.Running { + if !containerInfo.Container.State.Running { // The container isn't running anyway, so no need to kill it. return } - killErr := d.Docker.ContainerKill(context.Background(), r.containerID, "KILL") + _, killErr := d.Docker.ContainerKill(context.Background(), r.containerID, client.ContainerKillOptions{ + Signal: "SIGKILL", + }) if killErr != nil { d.log("%s : Failed to kill container %s: %s\n", r.contextStr, r.containerID, killErr) } @@ -323,7 +324,7 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) { // then incurs a slow recovery process when we use the blueprint later. d.log("%s: Stopping container: %s", res.contextStr, res.containerID) tenSeconds := 10 - d.Docker.ContainerStop(context.Background(), res.containerID, container.StopOptions{ + d.Docker.ContainerStop(context.Background(), res.containerID, client.ContainerStopOptions{ Timeout: &tenSeconds, }) @@ -331,9 +332,9 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) { d.log("%s: Stopped container: %s", res.contextStr, res.containerID) // commit the container - commit, err := d.Docker.ContainerCommit(context.Background(), res.containerID, container.CommitOptions{ + commit, err := d.Docker.ContainerCommit(context.Background(), res.containerID, client.ContainerCommitOptions{ Author: "Complement", - Pause: true, + NoPause: false, Reference: "localhost/complement:" + res.contextStr, Changes: toChanges(labels), @@ -438,7 +439,7 @@ func generateASRegistrationYaml(as b.ApplicationService) string { // Name is guaranteed not to be empty when err == nil func createNetworkIfNotExists(docker *client.Client, pkgNamespace, blueprintName string) (networkName string, err error) { // check if a network already exists for this blueprint - nws, err := docker.NetworkList(context.Background(), network.ListOptions{ + nws, err := docker.NetworkList(context.Background(), client.NetworkListOptions{ Filters: label( "complement_pkg="+pkgNamespace, "complement_blueprint="+blueprintName, @@ -448,15 +449,15 @@ func createNetworkIfNotExists(docker *client.Client, pkgNamespace, blueprintName return "", fmt.Errorf("%s: failed to list networks. %w", blueprintName, err) } // return the existing network - if len(nws) > 0 { - if len(nws) > 1 { - log.Printf("WARNING: createNetworkIfNotExists got %d networks for pkg=%s blueprint=%s", len(nws), pkgNamespace, blueprintName) + if len(nws.Items) > 0 { + if len(nws.Items) > 1 { + log.Printf("WARNING: createNetworkIfNotExists got %d networks for pkg=%s blueprint=%s", len(nws.Items), pkgNamespace, blueprintName) } - return nws[0].Name, nil + return nws.Items[0].Name, nil } networkName = "complement_" + pkgNamespace + "_" + blueprintName // make a user-defined network so we get DNS based on the container name - nw, err := docker.NetworkCreate(context.Background(), networkName, network.CreateOptions{ + nw, err := docker.NetworkCreate(context.Background(), networkName, client.NetworkCreateOptions{ Labels: map[string]string{ complementLabel: blueprintName, "complement_blueprint": blueprintName, @@ -466,11 +467,11 @@ func createNetworkIfNotExists(docker *client.Client, pkgNamespace, blueprintName if err != nil { return "", fmt.Errorf("%s: failed to create docker network. %w", blueprintName, err) } - if nw.Warning != "" { + if len(nw.Warning) > 0 && nw.Warning[0] != "" { if nw.ID == "" { - return "", fmt.Errorf("%s: fatal warning while creating docker network. %s", blueprintName, nw.Warning) + return "", fmt.Errorf("%s: fatal warning while creating docker network. %s", blueprintName, nw.Warning[0]) } - log.Printf("WARNING: %s\n", nw.Warning) + log.Printf("WARNING: %s\n", nw.Warning[0]) } if nw.ID == "" { return "", fmt.Errorf("%s: unexpected empty ID while creating networkID", blueprintName) @@ -479,7 +480,7 @@ func createNetworkIfNotExists(docker *client.Client, pkgNamespace, blueprintName } func printLogs(docker *client.Client, containerID, contextStr string) { - reader, err := docker.ContainerLogs(context.Background(), containerID, container.LogsOptions{ + reader, err := docker.ContainerLogs(context.Background(), containerID, client.ContainerLogsOptions{ ShowStderr: true, ShowStdout: true, Follow: false, @@ -497,7 +498,7 @@ func printLogs(docker *client.Client, containerID, contextStr string) { func printPortBindingsOfAllComplementContainers(docker *client.Client, contextStr string) { ctx := context.Background() - containers, err := docker.ContainerList(ctx, container.ListOptions{ + containers, err := docker.ContainerList(ctx, client.ContainerListOptions{ All: true, Filters: label( complementLabel, @@ -510,10 +511,10 @@ func printPortBindingsOfAllComplementContainers(docker *client.Client, contextSt log.Printf("============== %s : START ALL COMPLEMENT DOCKER PORT BINDINGS ==============\n", contextStr) - for _, container := range containers { + for _, container := range containers.Items { log.Printf("Container: %s: %s", container.ID, container.Names) - inspectRes, err := docker.ContainerInspect(ctx, container.ID) + inspectRes, err := docker.ContainerInspect(ctx, container.ID, client.ContainerInspectOptions{}) if err != nil { log.Printf("%s : Failed to inspect container (%s) while trying to `printPortBindingsOfAllComplementContainers`: %s\n", contextStr, container.ID, err) return @@ -522,7 +523,7 @@ func printPortBindingsOfAllComplementContainers(docker *client.Client, contextSt // Print an example so it's easier to understand the output log.Printf(" (host) -> (container)\n") // Then print the actual port bindings - for containerPort, portBindings := range inspectRes.NetworkSettings.Ports { + for containerPort, portBindings := range inspectRes.Container.NetworkSettings.Ports { hostPortBindingStrings := make([]string, len(portBindings)) for portBindingIndex, portBinding := range portBindings { hostPortBindingStrings[portBindingIndex] = fmt.Sprintf("%s:%s", portBinding.HostIP, portBinding.HostPort) @@ -537,58 +538,68 @@ func printPortBindingsOfAllComplementContainers(docker *client.Client, contextSt } // endpoints transforms the homeserver ports into the base URL and federation base URL. -func endpoints(p nat.PortMap, hsPortBindingIP string, csPort, ssPort int) (baseURL, fedBaseURL string, err error) { +func endpoints(p network.PortMap, hsPortBindingIP string, csPort, ssPort int) (baseURL, fedBaseURL string, err error) { csapiPortBinding, err := findPortBinding(p, hsPortBindingIP, csPort) if err != nil { return "", "", fmt.Errorf("Problem finding CS API port: %s", err) } - baseURL = fmt.Sprintf("http://"+csapiPortBinding.HostIP+":%s", csapiPortBinding.HostPort) + baseURL = fmt.Sprintf("http://%s:%s", csapiPortBinding.HostIP, csapiPortBinding.HostPort) ssapiPortBinding, err := findPortBinding(p, hsPortBindingIP, ssPort) if err != nil { return "", "", fmt.Errorf("Problem finding SS API port: %s", err) } - fedBaseURL = fmt.Sprintf("https://"+ssapiPortBinding.HostIP+":%s", ssapiPortBinding.HostPort) + fedBaseURL = fmt.Sprintf("https://%s:%s", ssapiPortBinding.HostIP, ssapiPortBinding.HostPort) return } -// findPortBinding finds a matching port binding for the given host/port in the `nat.PortMap`. +// findPortBinding finds a matching port binding for the given host/port in the `network.PortMap`. // // This function will return the first port binding that matches the given host IP. If a // `0.0.0.0` binding is found, we will assume that it is listening on all interfaces, // including the `hsPortBindingIP`, and return a binding with the `hsPortBindingIP` as // the host IP. -func findPortBinding(p nat.PortMap, hsPortBindingIP string, port int) (portBinding nat.PortBinding, err error) { +func findPortBinding(p network.PortMap, hsPortBindingIP string, port int) (network.PortBinding, error) { portString := fmt.Sprintf("%d/tcp", port) - portBindings, ok := p[nat.Port(portString)] + parsedPort, err := network.ParsePort(portString) + if err != nil { + return network.PortBinding{}, fmt.Errorf("port %s failed to be parsed: %v", portString, err) + } + portBindings, ok := p[parsedPort] if !ok { - return nat.PortBinding{}, fmt.Errorf("port %s not exposed - exposed ports: %v", portString, p) + return network.PortBinding{}, fmt.Errorf("port %s not exposed - exposed ports: %v", portString, p) } if len(portBindings) == 0 { - return nat.PortBinding{}, fmt.Errorf("port %s exposed with not mapped port: %+v", portString, p) + return network.PortBinding{}, fmt.Errorf("port %s exposed with not mapped port: %+v", portString, p) } for _, pb := range portBindings { - if pb.HostIP == hsPortBindingIP { + pbHostIP := pb.HostIP.String() + if pbHostIP == hsPortBindingIP { return pb, nil - } else if pb.HostIP == "0.0.0.0" { + } else if pbHostIP == "0.0.0.0" { // `0.0.0.0` means "all interfaces", so we can assume that this will be listening // for connections from `hsPortBindingIP` as well. - return nat.PortBinding{ - HostIP: hsPortBindingIP, - HostPort: pb.HostPort, - }, nil - } else if pb.HostIP == "" && hsPortBindingIP == "127.0.0.1" { + return getPortBinding(hsPortBindingIP, pb.HostPort) + } else if pbHostIP == "" && hsPortBindingIP == "127.0.0.1" { // `HostIP` can be empty in certain environments (observed with podman v4.3.1). We // will assume this is only a binding for `127.0.0.1`. - return nat.PortBinding{ - HostIP: hsPortBindingIP, - HostPort: pb.HostPort, - }, nil + return getPortBinding(hsPortBindingIP, pb.HostPort) } } - return nat.PortBinding{}, fmt.Errorf("unable to find matching port binding for %s %s: %+v", hsPortBindingIP, portString, p) + return network.PortBinding{}, fmt.Errorf("unable to find matching port binding for %s %s: %+v", hsPortBindingIP, portString, p) +} + +func getPortBinding(hsPortBindingIP string, hostPort string) (network.PortBinding, error) { + hostAddr, err := netip.ParseAddr(hsPortBindingIP) + if err != nil { + return network.PortBinding{}, fmt.Errorf("hsPortBindingIP %s failed to be parsed: %v", hsPortBindingIP, err) + } + return network.PortBinding{ + HostIP: hostAddr, + HostPort: hostPort, + }, nil } type result struct { diff --git a/internal/docker/deployer.go b/internal/docker/deployer.go index 6255a9454..ad453aabd 100644 --- a/internal/docker/deployer.go +++ b/internal/docker/deployer.go @@ -30,14 +30,14 @@ import ( "sync" "time" - "github.com/docker/docker/client" "github.com/matrix-org/complement/internal" complementRuntime "github.com/matrix-org/complement/runtime" + "github.com/moby/moby/client" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/image" - "github.com/docker/docker/api/types/mount" - "github.com/docker/docker/api/types/network" + "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/image" + "github.com/moby/moby/api/types/mount" + "github.com/moby/moby/api/types/network" "github.com/matrix-org/complement/config" ) @@ -140,7 +140,7 @@ func (d *Deployer) Deploy(ctx context.Context, blueprintName string) (*Deploymen HS: make(map[string]*HomeserverDeployment), Config: d.config, } - images, err := d.Docker.ImageList(ctx, image.ListOptions{ + images, err := d.Docker.ImageList(ctx, client.ImageListOptions{ Filters: label( "complement_pkg="+d.config.PackageNamespace, "complement_blueprint="+blueprintName, @@ -149,7 +149,7 @@ func (d *Deployer) Deploy(ctx context.Context, blueprintName string) (*Deploymen if err != nil { return nil, fmt.Errorf("Deploy: failed to ImageList: %w", err) } - if len(images) == 0 { + if len(images.Items) == 0 { return nil, fmt.Errorf("Deploy: No images have been built for blueprint %s", blueprintName) } networkName, err := createNetworkIfNotExists(d.Docker, d.config.PackageNamespace, blueprintName) @@ -160,7 +160,7 @@ func (d *Deployer) Deploy(ctx context.Context, blueprintName string) (*Deploymen // deploy images in parallel var mu sync.Mutex // protects mutable values like the counter and errors var wg sync.WaitGroup - wg.Add(len(images)) // ensure we wait until all images have deployed + wg.Add(len(images.Items)) // ensure we wait until all images have deployed deployImg := func(img image.Summary) error { defer wg.Done() mu.Lock() @@ -197,7 +197,7 @@ func (d *Deployer) Deploy(ctx context.Context, blueprintName string) (*Deploymen } var lastErr error - for _, img := range images { + for _, img := range images.Items { go func(i image.Summary) { err := deployImg(i) if err != nil { @@ -224,7 +224,7 @@ func (d *Deployer) Destroy(dep *Deployment, printServerLogs bool, testName strin // If we want the logs we gracefully stop the containers to allow // the logs to be flushed. oneSecond := 1 - err := d.Docker.ContainerStop(context.Background(), hsDep.ContainerID, container.StopOptions{ + _, err := d.Docker.ContainerStop(context.Background(), hsDep.ContainerID, client.ContainerStopOptions{ Timeout: &oneSecond, }) if err != nil { @@ -247,7 +247,7 @@ func (d *Deployer) Destroy(dep *Deployment, printServerLogs bool, testName strin log.Printf("Post test script result: %s", string(result)) } - err = d.Docker.ContainerRemove(context.Background(), hsDep.ContainerID, container.RemoveOptions{ + _, err = d.Docker.ContainerRemove(context.Background(), hsDep.ContainerID, client.ContainerRemoveOptions{ Force: true, }) if err != nil { @@ -267,7 +267,7 @@ func (d *Deployer) executePostScript(hsDep *HomeserverDeployment, testName strin func (d *Deployer) PauseServer(hsDep *HomeserverDeployment) error { ctx := context.Background() - err := d.Docker.ContainerPause(ctx, hsDep.ContainerID) + _, err := d.Docker.ContainerPause(ctx, hsDep.ContainerID, client.ContainerPauseOptions{}) if err != nil { return fmt.Errorf("failed to pause container %s: %s", hsDep.ContainerID, err) } @@ -276,7 +276,7 @@ func (d *Deployer) PauseServer(hsDep *HomeserverDeployment) error { func (d *Deployer) UnpauseServer(hsDep *HomeserverDeployment) error { ctx := context.Background() - err := d.Docker.ContainerUnpause(ctx, hsDep.ContainerID) + _, err := d.Docker.ContainerUnpause(ctx, hsDep.ContainerID, client.ContainerUnpauseOptions{}) if err != nil { return fmt.Errorf("failed to unpause container %s: %s", hsDep.ContainerID, err) } @@ -286,7 +286,7 @@ func (d *Deployer) UnpauseServer(hsDep *HomeserverDeployment) error { func (d *Deployer) StopServer(hsDep *HomeserverDeployment) error { ctx := context.Background() secs := int(d.config.SpawnHSTimeout.Seconds()) - err := d.Docker.ContainerStop(ctx, hsDep.ContainerID, container.StopOptions{ + _, err := d.Docker.ContainerStop(ctx, hsDep.ContainerID, client.ContainerStopOptions{ Timeout: &secs, }) if err != nil { @@ -308,7 +308,7 @@ func (d *Deployer) Restart(hsDep *HomeserverDeployment) error { func (d *Deployer) StartServer(hsDep *HomeserverDeployment) error { ctx := context.Background() - err := d.Docker.ContainerStart(ctx, hsDep.ContainerID, container.StartOptions{}) + _, err := d.Docker.ContainerStart(ctx, hsDep.ContainerID, client.ContainerStartOptions{}) if err != nil { return fmt.Errorf("failed to start container %s: %s", hsDep.ContainerID, err) } @@ -375,49 +375,55 @@ func deployImage( log.Printf("Sharing %v host environment variables with container", env) } - body, err := docker.ContainerCreate(ctx, &container.Config{ - Image: imageID, - Env: env, - //Cmd: d.ImageArgs, - Labels: map[string]string{ - complementLabel: contextStr, - "complement_blueprint": blueprintName, - "complement_pkg": pkgNamespace, - "complement_hs_name": hsName, + body, err := docker.ContainerCreate(ctx, client.ContainerCreateOptions{ + Config: &container.Config{ + Image: imageID, + Env: env, + //Cmd: d.ImageArgs, + Labels: map[string]string{ + complementLabel: contextStr, + "complement_blueprint": blueprintName, + "complement_pkg": pkgNamespace, + "complement_hs_name": hsName, + }, }, - }, &container.HostConfig{ - CapAdd: []string{"NET_ADMIN"}, // TODO : this should be some sort of option - // We use `PublishAllPorts` because although Complement only requires the ports 8008 - // and 8448 to be accessible in the image, other custom out-of-repo tests may use - // additional ports that are specific to their own application. - // - // Ideally, we would only bind to `cfg.HSPortBindingIP` but there isn't a way to - // specify the `HostIP` when using `PublishAllPorts`. And although, we could specify - // a manual port mapping, it's not compatible with also having `PublishAllPorts` set - // to true (we run into `address already in use` errors). Binding to all interfaces - // means we're also listening on `cfg.HSPortBindingIP` so it's good enough. - PublishAllPorts: true, - ExtraHosts: extraHosts, - Mounts: mounts, - // https://docs.docker.com/engine/containers/resource_constraints/ - Resources: container.Resources{ - // Constrain the the number of CPU cores this container can use - // - // The number of CPU cores in 1e9 increments + HostConfig: &container.HostConfig{ + CapAdd: []string{"NET_ADMIN"}, // TODO : this should be some sort of option + // We use `PublishAllPorts` because although Complement only requires the ports 8008 + // and 8448 to be accessible in the image, other custom out-of-repo tests may use + // additional ports that are specific to their own application. // - // `NanoCPUs` is the option that is "Applicable to all platforms" instead of - // `CPUPeriod`/`CPUQuota` (Unix only) or `CPUCount`/`CPUPercent` (Windows only). - NanoCPUs: int64(cfg.ContainerCPUCores * 1e9), - // Constrain the maximum memory the container can use - Memory: cfg.ContainerMemoryBytes, + // Ideally, we would only bind to `cfg.HSPortBindingIP` but there isn't a way to + // specify the `HostIP` when using `PublishAllPorts`. And although, we could specify + // a manual port mapping, it's not compatible with also having `PublishAllPorts` set + // to true (we run into `address already in use` errors). Binding to all interfaces + // means we're also listening on `cfg.HSPortBindingIP` so it's good enough. + PublishAllPorts: true, + ExtraHosts: extraHosts, + Mounts: mounts, + // https://docs.docker.com/engine/containers/resource_constraints/ + Resources: container.Resources{ + // Constrain the the number of CPU cores this container can use + // + // The number of CPU cores in 1e9 increments + // + // `NanoCPUs` is the option that is "Applicable to all platforms" instead of + // `CPUPeriod`/`CPUQuota` (Unix only) or `CPUCount`/`CPUPercent` (Windows only). + NanoCPUs: int64(cfg.ContainerCPUCores * 1e9), + // Constrain the maximum memory the container can use + Memory: cfg.ContainerMemoryBytes, + }, }, - }, &network.NetworkingConfig{ - EndpointsConfig: map[string]*network.EndpointSettings{ - networkName: { - Aliases: []string{hsName}, + NetworkingConfig: &network.NetworkingConfig{ + EndpointsConfig: map[string]*network.EndpointSettings{ + networkName: { + Aliases: []string{hsName}, + }, }, }, - }, nil, containerName) + Platform: nil, + Name: containerName, + }) if err != nil { return nil, fmt.Errorf("ContainerCreate: %s", err) } @@ -472,7 +478,7 @@ func deployImage( return stubDeployment, fmt.Errorf("failed to copy CA key to container: %s", err) } - err = docker.ContainerStart(ctx, containerID, container.StartOptions{}) + _, err = docker.ContainerStart(ctx, containerID, client.ContainerStartOptions{}) if err != nil { return stubDeployment, fmt.Errorf("ContainerStart: %s", err) } @@ -493,11 +499,11 @@ func deployImage( ) } - inspect, err := docker.ContainerInspect(ctx, containerID) + inspect, err := docker.ContainerInspect(ctx, containerID, client.ContainerInspectOptions{}) if err != nil { return stubDeployment, fmt.Errorf("ContainerInspect: %s", err) } - for vol := range inspect.Config.Volumes { + for vol := range inspect.Container.Config.Volumes { log.Printf( "WARNING: %s has a named VOLUME %s - volumes can lead to unpredictable behaviour due to "+ "test pollution. Remove the VOLUME in the Dockerfile to suppress this message.", containerName, vol, @@ -508,9 +514,9 @@ func deployImage( BaseURL: baseURL, FedBaseURL: fedBaseURL, ContainerID: containerID, - AccessTokens: tokensFromLabels(inspect.Config.Labels), - ApplicationServices: asIDToRegistrationFromLabels(inspect.Config.Labels), - DeviceIDs: deviceIDsFromLabels(inspect.Config.Labels), + AccessTokens: tokensFromLabels(inspect.Container.Config.Labels), + ApplicationServices: asIDToRegistrationFromLabels(inspect.Container.Config.Labels), + DeviceIDs: deviceIDsFromLabels(inspect.Container.Config.Labels), Network: networkName, } @@ -543,7 +549,9 @@ func copyToContainer(docker *client.Client, containerID, path string, data []byt tw.Close() // Put our new fake file in the container volume - err = docker.CopyToContainer(context.Background(), containerID, "/", &buf, container.CopyToContainerOptions{ + _, err = docker.CopyToContainer(context.Background(), containerID, client.CopyToContainerOptions{ + DestinationPath: "/", + Content: &buf, AllowOverwriteDirWithFile: false, }) if err != nil { @@ -567,12 +575,12 @@ func assertHostnameEqual(inputUrl string, expectedHostname string) error { // getHostAccessibleHomeserverURLs returns URLs that are accessible from the host // machine (outside the container) for the homeserver's client API and federation API. func getHostAccessibleHomeserverURLs(ctx context.Context, docker *client.Client, containerID string, hsPortBindingIP string) (baseURL string, fedBaseURL string, err error) { - inspectResponse, err := inspectContainer(ctx, docker, containerID) + inspectResult, err := inspectContainer(ctx, docker, containerID) if err != nil { return "", "", fmt.Errorf("failed to inspect ports: %w", err) } - baseURL, fedBaseURL, err = endpoints(inspectResponse.NetworkSettings.Ports, hsPortBindingIP, 8008, 8448) + baseURL, fedBaseURL, err = endpoints(inspectResult.Container.NetworkSettings.Ports, hsPortBindingIP, 8008, 8448) // Sanity check that the URLs match the expected configured binding IP. It's // also important that we use the canonical publicly accessible hostname for the @@ -595,15 +603,15 @@ func waitForPorts(ctx context.Context, docker *client.Client, containerID string // We need to hammer the inspect endpoint until the ports show up, they don't appear immediately. inspectStartTime := time.Now() for time.Since(inspectStartTime) < time.Second { - inspectResponse, err := inspectContainer(ctx, docker, containerID) + inspectResult, err := inspectContainer(ctx, docker, containerID) if inspectionErr, ok := err.(*containerInspectionError); ok && inspectionErr.Fatal { // If the error is fatal, we should not retry. return fmt.Errorf("Fatal inspection error: %s", err) } // Check to see if we can see the ports yet - _, csPortErr := findPortBinding(inspectResponse.NetworkSettings.Ports, hsPortBindingIP, 8008) - _, ssPortErr := findPortBinding(inspectResponse.NetworkSettings.Ports, hsPortBindingIP, 8448) + _, csPortErr := findPortBinding(inspectResult.Container.NetworkSettings.Ports, hsPortBindingIP, 8008) + _, ssPortErr := findPortBinding(inspectResult.Container.NetworkSettings.Ports, hsPortBindingIP, 8448) if csPortErr == nil && ssPortErr == nil { break } @@ -630,23 +638,23 @@ func inspectContainer( ctx context.Context, docker *client.Client, containerID string, -) (inspectResponse container.InspectResponse, err error) { - inspectResponse, err = docker.ContainerInspect(ctx, containerID) +) (inspectResult client.ContainerInspectResult, err error) { + inspectResult, err = docker.ContainerInspect(ctx, containerID, client.ContainerInspectOptions{}) if err != nil { - return container.InspectResponse{}, &containerInspectionError{ + return client.ContainerInspectResult{}, &containerInspectionError{ msg: err.Error(), Fatal: false, } } - if inspectResponse.State != nil && !inspectResponse.State.Running { + if inspectResult.Container.State != nil && !inspectResult.Container.State.Running { // the container exited, bail out with a container ID for logs - return container.InspectResponse{}, &containerInspectionError{ - msg: fmt.Sprintf("container (%s) is not running, state=%v", containerID, inspectResponse.State.Status), + return client.ContainerInspectResult{}, &containerInspectionError{ + msg: fmt.Sprintf("container (%s) is not running, state=%v", containerID, inspectResult.Container.State.Status), Fatal: true, } } - return inspectResponse, nil + return inspectResult, nil } // waitForContainer waits until a homeserver deployment is ready to serve requests. @@ -660,15 +668,15 @@ func waitForContainer(ctx context.Context, docker *client.Client, hsDep *Homeser lastErr = fmt.Errorf("timed out checking for homeserver to be up: %s", lastErr) return } - inspect, err := docker.ContainerInspect(ctx, hsDep.ContainerID) + inspect, err := docker.ContainerInspect(ctx, hsDep.ContainerID, client.ContainerInspectOptions{}) if err != nil { lastErr = fmt.Errorf("inspect container %s => error: %s", hsDep.ContainerID, err) time.Sleep(50 * time.Millisecond) continue } - if inspect.State.Health != nil && - inspect.State.Health.Status != "healthy" { - lastErr = fmt.Errorf("inspect container %s => health: %s", hsDep.ContainerID, inspect.State.Health.Status) + if inspect.Container.State.Health != nil && + inspect.Container.State.Health.Status != "healthy" { + lastErr = fmt.Errorf("inspect container %s => health: %s", hsDep.ContainerID, inspect.Container.State.Health.Status) time.Sleep(50 * time.Millisecond) continue } diff --git a/internal/docker/labels.go b/internal/docker/labels.go index 98f5caf94..69ed96fc9 100644 --- a/internal/docker/labels.go +++ b/internal/docker/labels.go @@ -3,15 +3,15 @@ package docker import ( "strings" - "github.com/docker/docker/api/types/filters" + "github.com/moby/moby/client" "github.com/matrix-org/complement/b" ) // label returns a filter for the presence of certain labels ("complement_context") or a match of // labels ("complement_blueprint=foo"). -func label(labelFilters ...string) filters.Args { - f := filters.NewArgs() +func label(labelFilters ...string) client.Filters { + f := client.Filters{} // label= or label== for _, in := range labelFilters { f.Add("label", in) diff --git a/runtime/hs.go b/runtime/hs.go index 2037953fa..740f5027f 100644 --- a/runtime/hs.go +++ b/runtime/hs.go @@ -3,8 +3,8 @@ package runtime import ( "context" - "github.com/docker/docker/client" "github.com/matrix-org/complement/ct" + "github.com/moby/moby/client" ) const ( @@ -18,8 +18,11 @@ var Homeserver string // ContainerKillFunc is used to destroy a container, it can be overwritten by Homeserver implementations // to e.g. gracefully stop a container. -var ContainerKillFunc = func(client *client.Client, containerID string) error { - return client.ContainerKill(context.Background(), containerID, "KILL") +var ContainerKillFunc = func(cli *client.Client, containerID string) error { + _, err := cli.ContainerKill(context.Background(), containerID, client.ContainerKillOptions{ + Signal: "SIGKILL", + }) + return err } // Skip the test (via t.Skipf) if the homeserver being tested matches one of the homeservers, else return. diff --git a/runtime/hs_dendrite.go b/runtime/hs_dendrite.go index 54ad99281..9f443ce06 100644 --- a/runtime/hs_dendrite.go +++ b/runtime/hs_dendrite.go @@ -6,8 +6,7 @@ package runtime import ( "context" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/client" + "github.com/moby/moby/client" ) func init() { @@ -16,7 +15,7 @@ func init() { // extract e.g. coverage reports. ContainerKillFunc = func(client *client.Client, containerID string) error { oneSecond := 1 - return client.ContainerStop(context.Background(), containerID, container.StopOptions{ + return client.ContainerStop(context.Background(), containerID, client.ContainerStopOptions{ Timeout: &oneSecond, }) } From 00acbf1c91d656376c5afa5684d86b031fc27113 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 4 Aug 2026 10:17:06 -0400 Subject: [PATCH 15/18] Migrate from ContainerStatsOneShot --- cmd/perftest/snapshot.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/perftest/snapshot.go b/cmd/perftest/snapshot.go index ef9b28920..9b407822c 100644 --- a/cmd/perftest/snapshot.go +++ b/cmd/perftest/snapshot.go @@ -7,6 +7,7 @@ import ( "github.com/matrix-org/complement/internal/docker" "github.com/moby/moby/api/types/container" + "github.com/moby/moby/client" ) type Snapshot struct { @@ -26,7 +27,7 @@ type Snapshot struct { func snapshotStats(spanName, desc string, deployment *docker.Deployment, absDuration, duration time.Duration) (snapshots []Snapshot) { for hsName, hsInfo := range deployment.HS { - stats, err := deployment.Deployer.Docker.ContainerStatsOneShot(context.Background(), hsInfo.ContainerID) + stats, err := deployment.Deployer.Docker.ContainerStats(context.Background(), hsInfo.ContainerID, client.ContainerStatsOptions{}) if err != nil { return nil } From 33290a0f686977d5c3815728fe305a152e65496a Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 4 Aug 2026 13:34:03 -0400 Subject: [PATCH 16/18] Fix runtime/hs_dendrite.go --- runtime/hs_dendrite.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runtime/hs_dendrite.go b/runtime/hs_dendrite.go index 9f443ce06..a80899e76 100644 --- a/runtime/hs_dendrite.go +++ b/runtime/hs_dendrite.go @@ -13,10 +13,11 @@ func init() { Homeserver = Dendrite // For Dendrite, we want to always stop the container gracefully, as this is needed to // extract e.g. coverage reports. - ContainerKillFunc = func(client *client.Client, containerID string) error { + ContainerKillFunc = func(cli *client.Client, containerID string) error { oneSecond := 1 - return client.ContainerStop(context.Background(), containerID, client.ContainerStopOptions{ + _, err := cli.ContainerStop(context.Background(), containerID, client.ContainerStopOptions{ Timeout: &oneSecond, }) + return err } } From f8b6e088dc242156c0e6a43eed09f7d3d4977812 Mon Sep 17 00:00:00 2001 From: timedout Date: Wed, 5 Aug 2026 12:11:49 +0100 Subject: [PATCH 17/18] Add Venator homeserver blacklist build tag (#902) Docs for running complement against Venator: https://demo.thefifthfleet.net/docs/contributing/testing.html --------- Signed-off-by: timedout --- runtime/hs.go | 18 ++++ runtime/hs_venator.go | 7 ++ .../account_change_password_pushers_test.go | 6 +- tests/csapi/account_change_password_test.go | 5 ++ tests/csapi/account_deactivate_test.go | 7 ++ tests/csapi/apidoc_content_test.go | 2 + tests/csapi/apidoc_device_management_test.go | 5 ++ tests/csapi/apidoc_presence_test.go | 5 +- tests/csapi/apidoc_register_test.go | 13 +++ tests/csapi/apidoc_room_alias_test.go | 8 ++ tests/csapi/apidoc_room_create_test.go | 2 + tests/csapi/apidoc_room_forget_test.go | 3 + tests/csapi/apidoc_room_state_test.go | 5 ++ tests/csapi/device_lists_test.go | 26 +++++- tests/csapi/invalid_test.go | 4 + tests/csapi/keychanges_test.go | 4 + tests/csapi/media_async_uploads_test.go | 3 + tests/csapi/media_misc_test.go | 2 + tests/csapi/power_levels_test.go | 5 ++ tests/csapi/room_leave_test.go | 11 +++ tests/csapi/room_messages_test.go | 2 + tests/csapi/room_upgrade_test.go | 3 + tests/csapi/rooms_members_local_test.go | 4 + tests/csapi/rooms_state_test.go | 6 ++ tests/csapi/sync_archive_test.go | 11 ++- tests/csapi/sync_test.go | 10 +++ tests/csapi/txnid_test.go | 4 +- tests/csapi/url_preview_test.go | 2 + tests/direct_messaging_test.go | 3 + tests/federation_acl_test.go | 4 + tests/federation_device_list_update_test.go | 4 + tests/federation_event_auth_test.go | 4 + tests/federation_media_content_test.go | 7 +- tests/federation_presence_test.go | 4 + tests/federation_query_profile_test.go | 4 + tests/federation_redaction_test.go | 4 + tests/federation_room_alias_test.go | 3 + tests/federation_room_ban_test.go | 4 + tests/federation_room_event_auth_test.go | 6 +- ...federation_room_get_missing_events_test.go | 4 + tests/federation_room_invite_test.go | 4 + tests/federation_room_join_test.go | 40 +++++++++ tests/federation_room_send_test.go | 4 + tests/federation_room_typing_test.go | 4 + tests/federation_rooms_invite_test.go | 6 +- tests/federation_sync_test.go | 4 + tests/federation_to_device_test.go | 4 + tests/federation_unreject_rejected_test.go | 4 + tests/federation_upload_keys_test.go | 4 + tests/knock_restricted_test.go | 7 ++ tests/knocking_test.go | 7 ++ tests/media_filename_test.go | 83 ++++++++++--------- tests/media_nofilename_test.go | 4 + tests/media_thumbnail_test.go | 7 ++ tests/restricted_room_hierarchy_test.go | 3 + tests/restricted_rooms_test.go | 10 +++ tests/room_hierarchy_test.go | 3 + tests/room_timestamp_to_event_test.go | 3 + tests/unknown_endpoints_test.go | 7 +- tests/v12_test.go | 16 ++++ 60 files changed, 402 insertions(+), 56 deletions(-) create mode 100644 runtime/hs_venator.go diff --git a/runtime/hs.go b/runtime/hs.go index 2037953fa..748efbe76 100644 --- a/runtime/hs.go +++ b/runtime/hs.go @@ -2,6 +2,7 @@ package runtime import ( "context" + "slices" "github.com/docker/docker/client" "github.com/matrix-org/complement/ct" @@ -12,6 +13,7 @@ const ( Synapse = "synapse" Conduit = "conduit" Conduwuit = "conduwuit" + Venator = "venator" ) var Homeserver string @@ -50,3 +52,19 @@ func SkipIf(t ct.TestLike, hses ...string) { ) } } + +// SkipUnless is the inverse of SkipIf: if the homeserver being tested is not present in the provided set, the test is skipped. +// This also means running without a blacklist tag will always skip. +func SkipUnless(t ct.TestLike, hses ...string) { + t.Helper() + if slices.Contains(hses, Homeserver) { + return + } + if Homeserver == "" { + t.Logf( + "WARNING: %s called runtime.SkipUnless(%v) but Complement doesn't know which HS is running as it was run without a *_blacklist tag: not executing test.", + t.Name(), hses, + ) + } + t.Skipf("test only runs on specific homeservers: %v", hses) +} diff --git a/runtime/hs_venator.go b/runtime/hs_venator.go new file mode 100644 index 000000000..6c5de40df --- /dev/null +++ b/runtime/hs_venator.go @@ -0,0 +1,7 @@ +//go:build venator_blacklist + +package runtime + +func init() { + Homeserver = Venator +} diff --git a/tests/csapi/account_change_password_pushers_test.go b/tests/csapi/account_change_password_pushers_test.go index 6607c002a..db95271d4 100644 --- a/tests/csapi/account_change_password_pushers_test.go +++ b/tests/csapi/account_change_password_pushers_test.go @@ -1,5 +1,7 @@ -//go:build !dendrite_blacklist -// +build !dendrite_blacklist +//go:build !dendrite_blacklist && !venator_blacklist +// +build !dendrite_blacklist,!venator_blacklist + +// Venator: https://github.com/matrix-org/complement/issues/897 package csapi_tests diff --git a/tests/csapi/account_change_password_test.go b/tests/csapi/account_change_password_test.go index 671a686ff..3d65fae21 100644 --- a/tests/csapi/account_change_password_test.go +++ b/tests/csapi/account_change_password_test.go @@ -9,11 +9,16 @@ import ( "github.com/matrix-org/complement/helpers" "github.com/matrix-org/complement/match" "github.com/matrix-org/complement/must" + "github.com/matrix-org/complement/runtime" "github.com/tidwall/gjson" ) func TestChangePassword(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/897 + // Omitting the entire file via build tag breaks other test files due to the definition of createSession + runtime.SkipIf(t, runtime.Venator) + deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) password1 := "superuser" diff --git a/tests/csapi/account_deactivate_test.go b/tests/csapi/account_deactivate_test.go index 9b2eaaa83..cffc5dd5a 100644 --- a/tests/csapi/account_deactivate_test.go +++ b/tests/csapi/account_deactivate_test.go @@ -4,6 +4,7 @@ import ( "net/http" "testing" + "github.com/matrix-org/complement/runtime" "github.com/tidwall/gjson" "github.com/matrix-org/complement" @@ -73,6 +74,8 @@ func TestDeactivateAccount(t *testing.T) { // sytest: Can't deactivate account with wrong password t.Run("Can't deactivate account with wrong password", func(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/898 + runtime.SkipIf(t, runtime.Venator) res := deactivateAccount(t, authedClient, "wrong_password") must.MatchResponse(t, res, match.HTTPResponse{ StatusCode: 401, @@ -83,6 +86,8 @@ func TestDeactivateAccount(t *testing.T) { }) // sytest: Can deactivate account t.Run("Can deactivate account", func(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/898 + runtime.SkipIf(t, runtime.Venator) res := deactivateAccount(t, authedClient, password) must.MatchResponse(t, res, match.HTTPResponse{ @@ -91,6 +96,8 @@ func TestDeactivateAccount(t *testing.T) { }) // sytest: After deactivating account, can't log in with password t.Run("After deactivating account, can't log in with password", func(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/898 + runtime.SkipIf(t, runtime.Venator) reqBody := client.WithJSONBody(t, map[string]interface{}{ "identifier": map[string]interface{}{ diff --git a/tests/csapi/apidoc_content_test.go b/tests/csapi/apidoc_content_test.go index 522f6e2e9..72fa9e36b 100644 --- a/tests/csapi/apidoc_content_test.go +++ b/tests/csapi/apidoc_content_test.go @@ -14,6 +14,8 @@ import ( func TestContent(t *testing.T) { // Synapse no longer allows downloads over the unauthenticated media endpoints by default runtime.SkipIf(t, runtime.Synapse) + // Venator is too young for any media to be available over the unauthenticated API, and always returns M_NOT_FOUND + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) diff --git a/tests/csapi/apidoc_device_management_test.go b/tests/csapi/apidoc_device_management_test.go index 430ba39bf..9c811e86e 100644 --- a/tests/csapi/apidoc_device_management_test.go +++ b/tests/csapi/apidoc_device_management_test.go @@ -3,6 +3,7 @@ package csapi_tests import ( "testing" + "github.com/matrix-org/complement/runtime" "github.com/tidwall/gjson" "github.com/matrix-org/complement" @@ -126,6 +127,8 @@ func TestDeviceManagement(t *testing.T) { // sytest: DELETE /device/{deviceId} t.Run("DELETE /device/{deviceId}", func(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/899 + runtime.SkipIf(t, runtime.Venator) newDeviceID, session2 := createSession(t, deployment, authedClient.UserID, "superuser") session2.MustSync(t, client.SyncReq{}) @@ -196,6 +199,8 @@ func TestDeviceManagement(t *testing.T) { }) // sytest: DELETE /device/{deviceId} requires UI auth user to match device owner t.Run("DELETE /device/{deviceId} requires UI auth user to match device owner", func(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/899 + runtime.SkipIf(t, runtime.Venator) bob := deployment.Register(t, "hs1", helpers.RegistrationOpts{ LocalpartSuffix: "bob", Password: "bobspassword", diff --git a/tests/csapi/apidoc_presence_test.go b/tests/csapi/apidoc_presence_test.go index 937f9917e..b072b98f0 100644 --- a/tests/csapi/apidoc_presence_test.go +++ b/tests/csapi/apidoc_presence_test.go @@ -1,7 +1,8 @@ -//go:build !dendrite_blacklist -// +build !dendrite_blacklist +//go:build !dendrite_blacklist && !venator_blacklist +// +build !dendrite_blacklist,!venator_blacklist // Rationale for being included in Dendrite's blacklist: https://github.com/matrix-org/complement/pull/104#discussion_r617646624 +// Venator: Does not implement presence package csapi_tests diff --git a/tests/csapi/apidoc_register_test.go b/tests/csapi/apidoc_register_test.go index 965c08575..b876abeaf 100644 --- a/tests/csapi/apidoc_register_test.go +++ b/tests/csapi/apidoc_register_test.go @@ -11,6 +11,7 @@ import ( "net/url" "testing" + "github.com/matrix-org/complement/runtime" "github.com/tidwall/gjson" "github.com/matrix-org/complement" @@ -62,6 +63,8 @@ func TestRegistration(t *testing.T) { }) // sytest: POST /register can create a user t.Run("POST /register can create a user", func(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/893 + runtime.SkipIf(t, runtime.Venator) t.Parallel() res := unauthedClient.Do(t, "POST", []string{"_matrix", "client", "v3", "register"}, client.WithRawBody(json.RawMessage(`{ "auth": { @@ -79,6 +82,8 @@ func TestRegistration(t *testing.T) { }) // sytest: POST /register downcases capitals in usernames t.Run("POST /register downcases capitals in usernames", func(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/893 + runtime.SkipIf(t, runtime.Venator) t.Parallel() res := unauthedClient.Do(t, "POST", []string{"_matrix", "client", "v3", "register"}, client.WithRawBody(json.RawMessage(`{ "auth": { @@ -96,6 +101,8 @@ func TestRegistration(t *testing.T) { }) // sytest: POST /register returns the same device_id as that in the request t.Run("POST /register returns the same device_id as that in the request", func(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/893 + runtime.SkipIf(t, runtime.Venator) t.Parallel() deviceID := "my_device_id" res := unauthedClient.Do(t, "POST", []string{"_matrix", "client", "v3", "register"}, client.WithRawBody(json.RawMessage(`{ @@ -115,6 +122,8 @@ func TestRegistration(t *testing.T) { }) // sytest: POST /register rejects registration of usernames with '$q' t.Run("POST /register rejects usernames with special characters", func(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/893 + runtime.SkipIf(t, runtime.Venator) t.Parallel() specialChars := []string{ `!`, @@ -151,6 +160,8 @@ func TestRegistration(t *testing.T) { } }) t.Run("POST /register rejects if user already exists", func(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/893 + runtime.SkipIf(t, runtime.Venator) t.Parallel() res := unauthedClient.Do(t, "POST", []string{"_matrix", "client", "v3", "register"}, client.WithRawBody(json.RawMessage(`{ "auth": { @@ -178,6 +189,8 @@ func TestRegistration(t *testing.T) { }) // sytest: POST /register allows registration of usernames with '$chr' t.Run("POST /register allows registration of usernames with ", func(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/893 + runtime.SkipIf(t, runtime.Venator) testChars := []rune("q3._=-/") for x := range testChars { localpart := fmt.Sprintf("chrtestuser%s", string(testChars[x])) diff --git a/tests/csapi/apidoc_room_alias_test.go b/tests/csapi/apidoc_room_alias_test.go index 921c8c6ff..1a4c05f39 100644 --- a/tests/csapi/apidoc_room_alias_test.go +++ b/tests/csapi/apidoc_room_alias_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/matrix-org/complement/runtime" "github.com/tidwall/gjson" "github.com/matrix-org/complement" @@ -279,6 +280,8 @@ func TestRoomDeleteAlias(t *testing.T) { // sytest: Can delete canonical alias t.Run("Can delete canonical alias", func(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/900 + runtime.SkipIf(t, runtime.Venator) t.Parallel() roomID := alice.MustCreateRoom(t, map[string]interface{}{}) @@ -393,6 +396,8 @@ func TestRoomDeleteAlias(t *testing.T) { // sytest: Users can't delete other's aliases t.Run("Users can't delete other's aliases", func(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/900 + runtime.SkipIf(t, runtime.Venator) t.Parallel() roomID := alice.MustCreateRoom(t, map[string]interface{}{}) @@ -426,6 +431,9 @@ func TestRoomDeleteAlias(t *testing.T) { // sytest: Users with sufficient power-level can delete other's aliases t.Run("Users with sufficient power-level can delete other's aliases", func(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/900 + // This technically passes, but not because of the behaviour this test is testing for. + runtime.SkipIf(t, runtime.Venator) t.Parallel() roomID := alice.MustCreateRoom(t, map[string]interface{}{}) diff --git a/tests/csapi/apidoc_room_create_test.go b/tests/csapi/apidoc_room_create_test.go index 1b5e63bba..3a8960edc 100644 --- a/tests/csapi/apidoc_room_create_test.go +++ b/tests/csapi/apidoc_room_create_test.go @@ -158,6 +158,8 @@ func TestRoomCreate(t *testing.T) { }) // sytest: POST /createRoom creates a room with the given version t.Run("POST /createRoom creates a room with the given version", func(t *testing.T) { + // Venator: does not support room v2 (>=v10 only) + runtime.SkipIf(t, runtime.Venator) t.Parallel() roomID := alice.MustCreateRoom(t, map[string]interface{}{ "room_version": "2", diff --git a/tests/csapi/apidoc_room_forget_test.go b/tests/csapi/apidoc_room_forget_test.go index e292a301e..87048d055 100644 --- a/tests/csapi/apidoc_room_forget_test.go +++ b/tests/csapi/apidoc_room_forget_test.go @@ -6,6 +6,7 @@ import ( "net/url" "testing" + "github.com/matrix-org/complement/runtime" "github.com/tidwall/gjson" "github.com/matrix-org/complement" @@ -19,6 +20,8 @@ import ( // These tests ensure that forgetting about rooms works as intended func TestRoomForget(t *testing.T) { + // Venator: does not implement manual room forgetting (is always done automatically on leave) + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) diff --git a/tests/csapi/apidoc_room_state_test.go b/tests/csapi/apidoc_room_state_test.go index 68489bd89..ff085efc3 100644 --- a/tests/csapi/apidoc_room_state_test.go +++ b/tests/csapi/apidoc_room_state_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/matrix-org/complement/runtime" "github.com/tidwall/gjson" "github.com/matrix-org/complement" @@ -59,6 +60,8 @@ func TestRoomState(t *testing.T) { }) // sytest: GET /rooms/:room_id/state/m.room.power_levels fetches powerlevels t.Run("GET /rooms/:room_id/state/m.room.power_levels fetches powerlevels", func(t *testing.T) { + // Venator doesn't marshal default values so the presence check fails despite being valid. + runtime.SkipIf(t, runtime.Venator) t.Parallel() roomID := authedClient.MustCreateRoom(t, map[string]interface{}{ @@ -329,6 +332,8 @@ func TestRoomState(t *testing.T) { }) }) t.Run("GET /rooms/:room_id/joined_members is forbidden after leaving room", func(t *testing.T) { + // Venator: does not implement API version r0 + runtime.SkipIf(t, runtime.Venator) t.Parallel() roomID := authedClient.MustCreateRoom(t, map[string]interface{}{}) authedClient.MustLeaveRoom(t, roomID) diff --git a/tests/csapi/device_lists_test.go b/tests/csapi/device_lists_test.go index 7012efa70..2b93cf465 100644 --- a/tests/csapi/device_lists_test.go +++ b/tests/csapi/device_lists_test.go @@ -449,16 +449,34 @@ func TestDeviceListUpdates(t *testing.T) { defer deployment.Destroy(t) t.Run("when local user joins a room", func(t *testing.T) { testOtherUserJoin(t, deployment, "hs1", "hs1") }) - t.Run("when remote user joins a room", func(t *testing.T) { testOtherUserJoin(t, deployment, "hs1", "hs2") }) + t.Run("when remote user joins a room", func(t *testing.T) { + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) + testOtherUserJoin(t, deployment, "hs1", "hs2") + }) t.Run("when joining a room with a local user", func(t *testing.T) { testJoin(t, deployment, "hs1", "hs1") }) - t.Run("when joining a room with a remote user", func(t *testing.T) { testJoin(t, deployment, "hs1", "hs2") }) + t.Run("when joining a room with a remote user", func(t *testing.T) { + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) + testJoin(t, deployment, "hs1", "hs2") + }) t.Run("when local user leaves a room", func(t *testing.T) { testOtherUserLeave(t, deployment, "hs1", "hs1") }) - t.Run("when remote user leaves a room", func(t *testing.T) { testOtherUserLeave(t, deployment, "hs1", "hs2") }) + t.Run("when remote user leaves a room", func(t *testing.T) { + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) + testOtherUserLeave(t, deployment, "hs1", "hs2") + }) t.Run("when leaving a room with a local user", func(t *testing.T) { testLeave(t, deployment, "hs1", "hs1") }) t.Run("when leaving a room with a remote user", func(t *testing.T) { runtime.SkipIf(t, runtime.Synapse) // FIXME: https://github.com/matrix-org/synapse/issues/13650 + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) testLeave(t, deployment, "hs1", "hs2") }) t.Run("when local user rejoins a room", func(t *testing.T) { testOtherUserRejoin(t, deployment, "hs1", "hs1") }) - t.Run("when remote user rejoins a room", func(t *testing.T) { testOtherUserRejoin(t, deployment, "hs1", "hs2") }) + t.Run("when remote user rejoins a room", func(t *testing.T) { + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) + testOtherUserRejoin(t, deployment, "hs1", "hs2") + }) } diff --git a/tests/csapi/invalid_test.go b/tests/csapi/invalid_test.go index 697ae832c..3ebd888d5 100644 --- a/tests/csapi/invalid_test.go +++ b/tests/csapi/invalid_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not support room version 6 (>=v10 only) + package csapi_tests import ( diff --git a/tests/csapi/keychanges_test.go b/tests/csapi/keychanges_test.go index 63437bdc3..d1ced440b 100644 --- a/tests/csapi/keychanges_test.go +++ b/tests/csapi/keychanges_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not implement r0 API, which part of this test depends on + package csapi_tests import ( diff --git a/tests/csapi/media_async_uploads_test.go b/tests/csapi/media_async_uploads_test.go index 49ca8252c..ccb4716c4 100644 --- a/tests/csapi/media_async_uploads_test.go +++ b/tests/csapi/media_async_uploads_test.go @@ -31,6 +31,9 @@ func TestAsyncUpload(t *testing.T) { }) t.Run("Not yet uploaded", func(t *testing.T) { + // Venator is too young for any media to be available over the unauthenticated API, + // and always returns M_NOT_FOUND + runtime.SkipIf(t, runtime.Venator) mxcURI := alice.CreateMedia(t) parts := strings.Split(mxcURI, "/") mediaID := parts[len(parts)-1] diff --git a/tests/csapi/media_misc_test.go b/tests/csapi/media_misc_test.go index 6efa39a59..900f3d8c5 100644 --- a/tests/csapi/media_misc_test.go +++ b/tests/csapi/media_misc_test.go @@ -63,6 +63,8 @@ func TestRoomImageRoundtrip(t *testing.T) { // sytest: Can read configuration endpoint func TestMediaConfig(t *testing.T) { + // Venator: does not permit the use of the legacy media API (always returns M_NOT_FOUND) + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) diff --git a/tests/csapi/power_levels_test.go b/tests/csapi/power_levels_test.go index 350837407..a28e6b818 100644 --- a/tests/csapi/power_levels_test.go +++ b/tests/csapi/power_levels_test.go @@ -4,6 +4,7 @@ import ( "fmt" "testing" + "github.com/matrix-org/complement/runtime" "github.com/tidwall/gjson" "github.com/matrix-org/complement" @@ -86,6 +87,10 @@ func TestPowerLevels(t *testing.T) { // sytest: GET /rooms/:room_id/state/m.room.power_levels can fetch levels t.Run("GET /rooms/:room_id/state/m.room.power_levels can fetch levels", func(t *testing.T) { + // Venator skips marshalling default values, which means all of these checks fail: + // > MatchJSONBytes key 'ban' missing with input = {} + // This is a feature :D + runtime.SkipIf(t, runtime.Venator) // Test if the old state still exists // note: before v10 we technically cannot assume that powerlevel integers are json numbers, // as they can be both strings and numbers. diff --git a/tests/csapi/room_leave_test.go b/tests/csapi/room_leave_test.go index b56edc348..8d44de3ac 100644 --- a/tests/csapi/room_leave_test.go +++ b/tests/csapi/room_leave_test.go @@ -5,6 +5,7 @@ import ( "strings" "testing" + "github.com/matrix-org/complement/runtime" "github.com/tidwall/gjson" "github.com/matrix-org/complement" @@ -127,6 +128,8 @@ func TestLeftRoomFixture(t *testing.T) { // sytest: Can get rooms/{roomId}/state for a departed room (SPEC-216) t.Run("Can get rooms/{roomId}/state for a departed room", func(t *testing.T) { + // Venator: rooms are automatically forgotten on leave, so this functionality conflicts + runtime.SkipIf(t, runtime.Venator) // Bob gets the old state content := bob.MustGetStateEventContent(t, roomID, madeUpStateKey, "") must.MatchGJSON(t, content, match.JSONKeyEqual("body", beforeMadeUpState)) @@ -138,6 +141,8 @@ func TestLeftRoomFixture(t *testing.T) { // sytest: Can get rooms/{roomId}/members for a departed room (SPEC-216) t.Run("Can get rooms/{roomId}/members for a departed room", func(t *testing.T) { + // Venator: rooms are automatically forgotten on leave, so this functionality conflicts + runtime.SkipIf(t, runtime.Venator) resp := bob.MustDo( t, "GET", @@ -163,6 +168,8 @@ func TestLeftRoomFixture(t *testing.T) { // sytest: Can get rooms/{roomId}/messages for a departed room (SPEC-216) t.Run("Can get rooms/{roomId}/messages for a departed room", func(t *testing.T) { + // Venator: rooms are automatically forgotten on leave, so this functionality conflicts + runtime.SkipIf(t, runtime.Venator) resp := bob.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "messages"}, client.WithQueries(url.Values{ "dir": []string{"b"}, "limit": []string{"3"}, @@ -188,6 +195,8 @@ func TestLeftRoomFixture(t *testing.T) { // sytest: Can get 'm.room.name' state for a departed room (SPEC-216) t.Run("Can get 'm.room.name' state for a departed room", func(t *testing.T) { + // Venator: rooms are automatically forgotten on leave, so this functionality conflicts + runtime.SkipIf(t, runtime.Venator) // Bob gets the old name content := bob.MustGetStateEventContent(t, roomID, "m.room.name", "") must.MatchGJSON(t, content, match.JSONKeyEqual("name", beforeRoomName)) @@ -199,6 +208,8 @@ func TestLeftRoomFixture(t *testing.T) { // sytest: Getting messages going forward is limited for a departed room (SPEC-216) t.Run("Getting messages going forward is limited for a departed room", func(t *testing.T) { + // Venator: rooms are automatically forgotten on leave, so this functionality conflicts + runtime.SkipIf(t, runtime.Venator) // TODO: try this with the most recent since token too resp := bob.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "messages"}, client.WithQueries(url.Values{ "dir": []string{"f"}, diff --git a/tests/csapi/room_messages_test.go b/tests/csapi/room_messages_test.go index b21aa55a7..4e2208ff8 100644 --- a/tests/csapi/room_messages_test.go +++ b/tests/csapi/room_messages_test.go @@ -242,6 +242,8 @@ type MessagesTestCase struct { } func TestMessagesOverFederation(t *testing.T) { + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 2) defer deployment.Destroy(t) diff --git a/tests/csapi/room_upgrade_test.go b/tests/csapi/room_upgrade_test.go index 17cd8686d..283b2cb3b 100644 --- a/tests/csapi/room_upgrade_test.go +++ b/tests/csapi/room_upgrade_test.go @@ -21,6 +21,9 @@ import ( // perhaps that just needs a clarification/MSC to document the state of things. Synapse // and Dendrite do this for example. func TestPushRuleRoomUpgrade(t *testing.T) { + // Venator: does not implement this functionality (needs spec change). + // Venator: additionally does not implement federation (which several tests depend on). + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 2) defer deployment.Destroy(t) diff --git a/tests/csapi/rooms_members_local_test.go b/tests/csapi/rooms_members_local_test.go index b715ddce1..d2db90847 100644 --- a/tests/csapi/rooms_members_local_test.go +++ b/tests/csapi/rooms_members_local_test.go @@ -52,6 +52,8 @@ func TestMembersLocal(t *testing.T) { // Split into initial and incremental sync cases in Complement. t.Run("Existing members see new members' presence (in initial sync)", func(t *testing.T) { runtime.SkipIf(t, runtime.Dendrite) // FIXME: https://github.com/matrix-org/matrix-spec/issues/1374 + // Venator: does not implement presence + runtime.SkipIf(t, runtime.Venator) t.Parallel() // First we sync to make sure bob to have joined the room... alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID)) @@ -65,6 +67,8 @@ func TestMembersLocal(t *testing.T) { // sytest: Existing members see new members' presence // Split into initial and incremental sync cases in Complement. t.Run("Existing members see new members' presence (in incremental sync)", func(t *testing.T) { + // Venator: does not implement presence + runtime.SkipIf(t, runtime.Venator) t.Parallel() alice.MustSyncUntil(t, client.SyncReq{Since: incrementalSyncTokenBeforeBobJoinsRoom}, client.SyncJoinedTo(bob.UserID, roomID), diff --git a/tests/csapi/rooms_state_test.go b/tests/csapi/rooms_state_test.go index 4f2a54510..e2657d624 100644 --- a/tests/csapi/rooms_state_test.go +++ b/tests/csapi/rooms_state_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/matrix-org/complement/runtime" "github.com/tidwall/gjson" "github.com/matrix-org/complement" @@ -89,6 +90,8 @@ func TestRoomCreationReportsEventsToMyself(t *testing.T) { // sytest: Setting state twice is idempotent t.Run("Setting state twice is idempotent", func(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/901 + runtime.SkipIf(t, runtime.Venator) t.Parallel() stateEvent := b.Event{ @@ -109,6 +112,9 @@ func TestRoomCreationReportsEventsToMyself(t *testing.T) { // sytest: Joining room twice is idempotent t.Run("Joining room twice is idempotent", func(t *testing.T) { + // Venator: https://github.com/matrix-org/complement/issues/901 + // Test passes illegitimately + runtime.SkipIf(t, runtime.Venator) t.Parallel() roomID := bob.MustCreateRoom(t, map[string]interface{}{ diff --git a/tests/csapi/sync_archive_test.go b/tests/csapi/sync_archive_test.go index f8ed18aa3..3d0dcb065 100644 --- a/tests/csapi/sync_archive_test.go +++ b/tests/csapi/sync_archive_test.go @@ -1,9 +1,10 @@ package csapi_tests import ( - "github.com/tidwall/gjson" "testing" + "github.com/tidwall/gjson" + "github.com/matrix-org/complement" "github.com/matrix-org/complement/b" "github.com/matrix-org/complement/client" @@ -49,6 +50,8 @@ func TestSyncLeaveSection(t *testing.T) { // sytest: Left rooms appear in the leave section of sync t.Run("Left rooms appear in the leave section of sync", func(t *testing.T) { + // Venator: rooms are automatically forgotten on leave, which results in conflicting behaviour + runtime.SkipIf(t, runtime.Venator) // SyncLeftFrom does "active" probing of rooms.leave if userID == clientUserID alice.MustSyncUntil(t, client.SyncReq{ Filter: includeLeaveFilter, @@ -57,6 +60,8 @@ func TestSyncLeaveSection(t *testing.T) { // sytest: Left rooms appear in the leave section of full state sync t.Run("Left rooms appear in the leave section of full state sync", func(t *testing.T) { + // Venator: rooms are automatically forgotten on leave, which results in conflicting behaviour + runtime.SkipIf(t, runtime.Venator) alice.MustSyncUntil(t, client.SyncReq{ Since: fullStateSince, Filter: includeLeaveFilter, @@ -75,6 +80,8 @@ func TestSyncLeaveSection(t *testing.T) { // sytest: Newly left rooms appear in the leave section of gapped sync func TestGappedSyncLeaveSection(t *testing.T) { runtime.SkipIf(t, runtime.Dendrite) // FIXME: https://github.com/matrix-org/dendrite/issues/1323 + // Venator: rooms are automatically forgotten on leave, which results in conflicting behaviour + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) @@ -117,6 +124,8 @@ func TestGappedSyncLeaveSection(t *testing.T) { // ... plus later additions func TestArchivedRoomsHistory(t *testing.T) { runtime.SkipIf(t, runtime.Dendrite) // FIXME: https://github.com/matrix-org/dendrite/issues/1323 + // Venator: rooms are automatically forgotten on leave, which results in conflicting behaviour + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) diff --git a/tests/csapi/sync_test.go b/tests/csapi/sync_test.go index 3dec9ea51..5a9d6b063 100644 --- a/tests/csapi/sync_test.go +++ b/tests/csapi/sync_test.go @@ -210,6 +210,8 @@ func TestSync(t *testing.T) { // sytest: Newly joined room includes presence in incremental sync t.Run("Newly joined room includes presence in incremental sync", func(t *testing.T) { runtime.SkipIf(t, runtime.Dendrite) // FIXME: https://github.com/matrix-org/dendrite/issues/1324 + // Venator: does not implement presence + runtime.SkipIf(t, runtime.Venator) roomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) _, nextBatch := bob.MustSync(t, client.SyncReq{}) @@ -230,6 +232,8 @@ func TestSync(t *testing.T) { // sytest: Get presence for newly joined members in incremental sync t.Run("Get presence for newly joined members in incremental sync", func(t *testing.T) { runtime.SkipIf(t, runtime.Dendrite) // FIXME: https://github.com/matrix-org/dendrite/issues/1324 + // Venator: does not implement presence + runtime.SkipIf(t, runtime.Venator) roomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) nextBatch := alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) sendMessages(t, alice, roomID, "dummy message", 1) @@ -276,6 +280,8 @@ func TestSync(t *testing.T) { // in the order we send them, but in practice it seems to get close // enough. + // Venator: does not yet implement federation, so this test cannot function (see third paragraph of above) + runtime.SkipIf(t, runtime.Venator) t.Parallel() // alice creates two rooms, which charlie (on our test server) joins @@ -450,6 +456,8 @@ func TestSync(t *testing.T) { // events, with the `limited` flag set. func TestSyncTimelineGap(t *testing.T) { runtime.SkipIf(t, runtime.Dendrite) + // Venator: does not yet implement federation (so this test cannot function) + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{}) @@ -626,6 +634,8 @@ func TestSyncTimelineGap(t *testing.T) { // Test presence from people in 2 different rooms in incremental sync func TestPresenceSyncDifferentRooms(t *testing.T) { + // Venator: does not implement presence + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) diff --git a/tests/csapi/txnid_test.go b/tests/csapi/txnid_test.go index 04fb8d1e4..621afe03a 100644 --- a/tests/csapi/txnid_test.go +++ b/tests/csapi/txnid_test.go @@ -214,8 +214,8 @@ func TestTxnIdempotency(t *testing.T) { // TestTxnIdWithRefreshToken tests that when a client refreshes its access token, // it still gets back a transaction ID in the sync response and idempotency is respected. func TestTxnIdWithRefreshToken(t *testing.T) { - // Dendrite and Conduit don't support refresh tokens yet. - runtime.SkipIf(t, runtime.Dendrite, runtime.Conduit) + // Dendrite, Conduit, and Venator don't support refresh tokens yet. + runtime.SkipIf(t, runtime.Dendrite, runtime.Conduit, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) diff --git a/tests/csapi/url_preview_test.go b/tests/csapi/url_preview_test.go index fc1ef2c7a..73b729d85 100644 --- a/tests/csapi/url_preview_test.go +++ b/tests/csapi/url_preview_test.go @@ -41,6 +41,8 @@ var oGraphHtml = fmt.Sprintf(` // sytest: Test URL preview func TestUrlPreview(t *testing.T) { runtime.SkipIf(t, runtime.Dendrite) // FIXME: https://github.com/matrix-org/dendrite/issues/621 + // Venator: does not allow the use of the unauthenticated media API (always returns M_NOT_FOUND) + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) diff --git a/tests/direct_messaging_test.go b/tests/direct_messaging_test.go index 2282507a4..bb06ae795 100644 --- a/tests/direct_messaging_test.go +++ b/tests/direct_messaging_test.go @@ -12,6 +12,7 @@ import ( "github.com/matrix-org/complement/helpers" "github.com/matrix-org/complement/match" "github.com/matrix-org/complement/must" + "github.com/matrix-org/complement/runtime" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib" @@ -102,6 +103,8 @@ func TestIsDirectFlagLocal(t *testing.T) { // Test that the `is_direct` flag on m.room.member invites propagate to the target user. Users // are on different homeservers. func TestIsDirectFlagFederation(t *testing.T) { + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) diff --git a/tests/federation_acl_test.go b/tests/federation_acl_test.go index 9810c5efc..7f7c5d8a6 100644 --- a/tests/federation_acl_test.go +++ b/tests/federation_acl_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not yet implement federation. ACLs are implemented, but not in a way that can be exposed to these tests + package tests import ( diff --git a/tests/federation_device_list_update_test.go b/tests/federation_device_list_update_test.go index 770535c8a..95ba69bc0 100644 --- a/tests/federation_device_list_update_test.go +++ b/tests/federation_device_list_update_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not yet implement federation + package tests import ( diff --git a/tests/federation_event_auth_test.go b/tests/federation_event_auth_test.go index f49b4e667..333a91c46 100644 --- a/tests/federation_event_auth_test.go +++ b/tests/federation_event_auth_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not yet implement federation + package tests import ( diff --git a/tests/federation_media_content_test.go b/tests/federation_media_content_test.go index c2cfd16ef..03f2a9d0a 100644 --- a/tests/federation_media_content_test.go +++ b/tests/federation_media_content_test.go @@ -1,11 +1,16 @@ +//go:build !venator_blacklist + +// Venator: does not yet implement federation + package tests import ( "bytes" + "testing" + "github.com/matrix-org/complement" "github.com/matrix-org/complement/helpers" "github.com/matrix-org/complement/internal/data" - "testing" ) func TestContentMediaV1(t *testing.T) { diff --git a/tests/federation_presence_test.go b/tests/federation_presence_test.go index 9e11ca7f1..c4d0cfd83 100644 --- a/tests/federation_presence_test.go +++ b/tests/federation_presence_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not yet implement federation, nor presence + package tests import ( diff --git a/tests/federation_query_profile_test.go b/tests/federation_query_profile_test.go index f901c7c6e..1c1c50a2d 100644 --- a/tests/federation_query_profile_test.go +++ b/tests/federation_query_profile_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not yet implement federation + package tests import ( diff --git a/tests/federation_redaction_test.go b/tests/federation_redaction_test.go index 94bb03a59..909c16b99 100644 --- a/tests/federation_redaction_test.go +++ b/tests/federation_redaction_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not yet implement federation + package tests import ( diff --git a/tests/federation_room_alias_test.go b/tests/federation_room_alias_test.go index a2a90c77d..ba412e1e6 100644 --- a/tests/federation_room_alias_test.go +++ b/tests/federation_room_alias_test.go @@ -8,10 +8,13 @@ import ( "github.com/matrix-org/complement/helpers" "github.com/matrix-org/complement/match" "github.com/matrix-org/complement/must" + "github.com/matrix-org/complement/runtime" ) // sytest: Remote room alias queries can handle Unicode func TestRemoteAliasRequestsUnderstandUnicode(t *testing.T) { + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 2) defer deployment.Destroy(t) diff --git a/tests/federation_room_ban_test.go b/tests/federation_room_ban_test.go index 29d3d53c2..9f370f04f 100644 --- a/tests/federation_room_ban_test.go +++ b/tests/federation_room_ban_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not yet implement federation + package tests import ( diff --git a/tests/federation_room_event_auth_test.go b/tests/federation_room_event_auth_test.go index f209b40c1..b17faccfd 100644 --- a/tests/federation_room_event_auth_test.go +++ b/tests/federation_room_event_auth_test.go @@ -1,6 +1,8 @@ // These tests currently fail on Dendrite, due to Dendrite bugs. -//go:build !dendrite_blacklist -// +build !dendrite_blacklist +//go:build !dendrite_blacklist && !venator_blacklist +// +build !dendrite_blacklist,!venator_blacklist + +// Venator: does not yet implement federation package tests diff --git a/tests/federation_room_get_missing_events_test.go b/tests/federation_room_get_missing_events_test.go index 9743e4325..d6ddaa546 100644 --- a/tests/federation_room_get_missing_events_test.go +++ b/tests/federation_room_get_missing_events_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not yet implement federation + package tests import ( diff --git a/tests/federation_room_invite_test.go b/tests/federation_room_invite_test.go index fca50315f..cf8f1bcdc 100644 --- a/tests/federation_room_invite_test.go +++ b/tests/federation_room_invite_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not yet implement federation + package tests import ( diff --git a/tests/federation_room_join_test.go b/tests/federation_room_join_test.go index 03b12ee03..bd18286fe 100644 --- a/tests/federation_room_join_test.go +++ b/tests/federation_room_join_test.go @@ -39,6 +39,10 @@ import ( // m.room.create event would pick that up. We also can't tear down the Complement // server because otherwise signing key lookups will fail. func TestJoinViaRoomIDAndServerName(t *testing.T) { + // Venator: does not yet implement federation. The entire file cannot be ignored as + // testValidationForSendMembershipEndpoint is referenced elsewhere, which causes compile errors if elided by build + // constraints. + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 2) defer deployment.Destroy(t) @@ -89,6 +93,10 @@ func TestJoinViaRoomIDAndServerName(t *testing.T) { // This tests that joining a room with multiple ?server_name=s works correctly. // The join should succeed even if the first server is not in the room. func TestJoinFederatedRoomFailOver(t *testing.T) { + // Venator: does not yet implement federation. The entire file cannot be ignored as + // testValidationForSendMembershipEndpoint is referenced elsewhere, which causes compile errors if elided by build + // constraints. + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 2) defer deployment.Destroy(t) @@ -132,6 +140,10 @@ func TestJoinFederatedRoomFailOver(t *testing.T) { // the properties listed above, then asking HS1 to join them and make sure that // they 200 OK. func TestJoinFederatedRoomWithUnverifiableEvents(t *testing.T) { + // Venator: does not yet implement federation. The entire file cannot be ignored as + // testValidationForSendMembershipEndpoint is referenced elsewhere, which causes compile errors if elided by build + // constraints. + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) @@ -298,6 +310,10 @@ func TestJoinFederatedRoomWithUnverifiableEvents(t *testing.T) { // This test checks that users cannot circumvent the auth checks via send_join. func TestBannedUserCannotSendJoin(t *testing.T) { + // Venator: does not yet implement federation. The entire file cannot be ignored as + // testValidationForSendMembershipEndpoint is referenced elsewhere, which causes compile errors if elided by build + // constraints. + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) @@ -365,21 +381,37 @@ func TestBannedUserCannotSendJoin(t *testing.T) { // This test checks that we cannot submit anything via /v1/send_join except a join. func TestCannotSendNonJoinViaSendJoinV1(t *testing.T) { + // Venator: does not yet implement federation. The entire file cannot be ignored as + // testValidationForSendMembershipEndpoint is referenced elsewhere, which causes compile errors if elided by build + // constraints. + runtime.SkipIf(t, runtime.Venator) testValidationForSendMembershipEndpoint(t, "/_matrix/federation/v1/send_join", "join", nil) } // This test checks that we cannot submit anything via /v2/send_join except a join. func TestCannotSendNonJoinViaSendJoinV2(t *testing.T) { + // Venator: does not yet implement federation. The entire file cannot be ignored as + // testValidationForSendMembershipEndpoint is referenced elsewhere, which causes compile errors if elided by build + // constraints. + runtime.SkipIf(t, runtime.Venator) testValidationForSendMembershipEndpoint(t, "/_matrix/federation/v2/send_join", "join", nil) } // This test checks that we cannot submit anything via /v1/send_leave except a leave. func TestCannotSendNonLeaveViaSendLeaveV1(t *testing.T) { + // Venator: does not yet implement federation. The entire file cannot be ignored as + // testValidationForSendMembershipEndpoint is referenced elsewhere, which causes compile errors if elided by build + // constraints. + runtime.SkipIf(t, runtime.Venator) testValidationForSendMembershipEndpoint(t, "/_matrix/federation/v1/send_leave", "leave", nil) } // This test checks that we cannot submit anything via /v2/send_leave except a leave. func TestCannotSendNonLeaveViaSendLeaveV2(t *testing.T) { + // Venator: does not yet implement federation. The entire file cannot be ignored as + // testValidationForSendMembershipEndpoint is referenced elsewhere, which causes compile errors if elided by build + // constraints. + runtime.SkipIf(t, runtime.Venator) testValidationForSendMembershipEndpoint(t, "/_matrix/federation/v2/send_leave", "leave", nil) } @@ -494,6 +526,10 @@ func testValidationForSendMembershipEndpoint(t *testing.T, baseApiPath, expected // // Will be skipped if the server returns a full-state response. func TestSendJoinPartialStateResponse(t *testing.T) { + // Venator: does not yet implement federation. The entire file cannot be ignored as + // testValidationForSendMembershipEndpoint is referenced elsewhere, which causes compile errors if elided by build + // constraints. + runtime.SkipIf(t, runtime.Venator) // start with a homeserver with two users deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) @@ -580,6 +616,10 @@ func typeAndStateKeyForEvent(result gjson.Result) string { } func TestJoinFederatedRoomFromApplicationServiceBridgeUser(t *testing.T) { + // Venator: does not yet implement federation. The entire file cannot be ignored as + // testValidationForSendMembershipEndpoint is referenced elsewhere, which causes compile errors if elided by build + // constraints. + runtime.SkipIf(t, runtime.Venator) // Dendrite doesn't read AS registration files from Complement yet runtime.SkipIf(t, runtime.Dendrite) // FIXME: https://github.com/matrix-org/complement/issues/514 diff --git a/tests/federation_room_send_test.go b/tests/federation_room_send_test.go index acc318be3..376e4bfbe 100644 --- a/tests/federation_room_send_test.go +++ b/tests/federation_room_send_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not yet implement federation + package tests import ( diff --git a/tests/federation_room_typing_test.go b/tests/federation_room_typing_test.go index 7e4a9b124..9fcb7bb02 100644 --- a/tests/federation_room_typing_test.go +++ b/tests/federation_room_typing_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not yet implement federation + package tests import ( diff --git a/tests/federation_rooms_invite_test.go b/tests/federation_rooms_invite_test.go index d5386014d..cff49d64a 100644 --- a/tests/federation_rooms_invite_test.go +++ b/tests/federation_rooms_invite_test.go @@ -1,6 +1,8 @@ // These tests currently fail on Dendrite, due to Dendrite bugs. -//go:build !dendrite_blacklist -// +build !dendrite_blacklist +//go:build !dendrite_blacklist && !venator_blacklist +// +build !dendrite_blacklist,!venator_blacklist + +// Venator: does not yet implement federation package tests diff --git a/tests/federation_sync_test.go b/tests/federation_sync_test.go index 23f67c5bb..44ab13bca 100644 --- a/tests/federation_sync_test.go +++ b/tests/federation_sync_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not yet implement federation + package tests import ( diff --git a/tests/federation_to_device_test.go b/tests/federation_to_device_test.go index 8a4d98ed0..1eb8634e0 100644 --- a/tests/federation_to_device_test.go +++ b/tests/federation_to_device_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not yet implement federation + package tests import ( diff --git a/tests/federation_unreject_rejected_test.go b/tests/federation_unreject_rejected_test.go index ef7f016b1..f2dad2df0 100644 --- a/tests/federation_unreject_rejected_test.go +++ b/tests/federation_unreject_rejected_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not yet implement federation + package tests import ( diff --git a/tests/federation_upload_keys_test.go b/tests/federation_upload_keys_test.go index 2f91485e9..feab976de 100644 --- a/tests/federation_upload_keys_test.go +++ b/tests/federation_upload_keys_test.go @@ -1,3 +1,7 @@ +//go:build !venator_blacklist + +// Venator: does not yet implement federation + package tests import ( diff --git a/tests/knock_restricted_test.go b/tests/knock_restricted_test.go index ec2035a3d..9876a8953 100644 --- a/tests/knock_restricted_test.go +++ b/tests/knock_restricted_test.go @@ -13,6 +13,7 @@ import ( "github.com/matrix-org/complement" "github.com/matrix-org/complement/helpers" + "github.com/matrix-org/complement/runtime" ) var ( @@ -32,6 +33,8 @@ func TestKnockRoomsInPublicRoomsDirectoryInMSC3787Room(t *testing.T) { // See TestCannotSendKnockViaSendKnock func TestCannotSendKnockViaSendKnockInMSC3787Room(t *testing.T) { + // Venator: does not yet implement federation. + runtime.SkipIf(t, runtime.Venator) testValidationForSendMembershipEndpoint(t, "/_matrix/federation/v1/send_knock", "knock", map[string]interface{}{ "preset": "public_chat", @@ -57,6 +60,8 @@ func TestRestrictedRoomsLocalJoinInMSC3787Room(t *testing.T) { // See TestRestrictedRoomsRemoteJoin func TestRestrictedRoomsRemoteJoinInMSC3787Room(t *testing.T) { + // Venator: does not yet implement federation. + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 2) defer deployment.Destroy(t) @@ -72,6 +77,8 @@ func TestRestrictedRoomsRemoteJoinInMSC3787Room(t *testing.T) { // See TestRestrictedRoomsRemoteJoinLocalUser func TestRestrictedRoomsRemoteJoinLocalUserInMSC3787Room(t *testing.T) { + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) doTestRestrictedRoomsRemoteJoinLocalUser(t, roomVersion, joinRule) } diff --git a/tests/knocking_test.go b/tests/knocking_test.go index 449911d91..ee9c7201d 100644 --- a/tests/knocking_test.go +++ b/tests/knocking_test.go @@ -16,6 +16,7 @@ import ( "time" "github.com/matrix-org/complement" + "github.com/matrix-org/complement/runtime" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/tidwall/gjson" @@ -40,6 +41,8 @@ func TestKnocking(t *testing.T) { } func doTestKnocking(t *testing.T, roomVersion string, joinRule string) { + // Venator: does not yet implement federation. + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 2) defer deployment.Destroy(t) @@ -361,6 +364,8 @@ func knockOnRoomWithStatus(t *testing.T, c *client.CSAPI, roomID, reason string, // representing a knock room. For sanity-checking, this test will also create a public room and ensure it has a // 'join_rule' representing a publicly-joinable room. func TestKnockRoomsInPublicRoomsDirectory(t *testing.T) { + // Venator: does not support room v7 (>=v10 only) + runtime.SkipIf(t, runtime.Venator) // v7 is required for knocking doTestKnockRoomsInPublicRoomsDirectory(t, "7", "knock") } @@ -451,6 +456,8 @@ func publishAndCheckRoomJoinRule(t *testing.T, c *client.CSAPI, roomID, expected // TestCannotSendNonKnockViaSendKnock checks that we cannot submit anything via /send_knock except a knock func TestCannotSendNonKnockViaSendKnock(t *testing.T) { + // Venator: does not yet implement federation, nor room v7 (>=v10 only) + runtime.SkipIf(t, runtime.Venator) testValidationForSendMembershipEndpoint(t, "/_matrix/federation/v1/send_knock", "knock", map[string]interface{}{ "preset": "public_chat", diff --git a/tests/media_filename_test.go b/tests/media_filename_test.go index d5a857d56..bbb742ebe 100644 --- a/tests/media_filename_test.go +++ b/tests/media_filename_test.go @@ -43,6 +43,9 @@ func TestMediaFilenames(t *testing.T) { t.Run(fmt.Sprintf("Can download file '%s'", filename), func(t *testing.T) { // Synapse no longer allows downloads over the unauthenticated media endpoints by default runtime.SkipIf(t, runtime.Synapse) + // Venator is too young for any media to be available over the unauthenticated API, + // and always returns M_NOT_FOUND + runtime.SkipIf(t, runtime.Venator) t.Parallel() mxcUri := alice.UploadContent(t, data.MatrixPng, filename, "image/png") @@ -73,6 +76,9 @@ func TestMediaFilenames(t *testing.T) { t.Run("Can download specifying a different ASCII file name", func(t *testing.T) { // Synapse no longer allows downloads over the unauthenticated media endpoints by default runtime.SkipIf(t, runtime.Synapse) + // Venator is too young for any media to be available over the unauthenticated API, + // and always returns M_NOT_FOUND + runtime.SkipIf(t, runtime.Venator) t.Parallel() mxcUri := alice.UploadContent(t, data.MatrixPng, asciiFileName, "image/png") @@ -113,6 +119,9 @@ func TestMediaFilenames(t *testing.T) { t.Run("Can download specifying a different Unicode file name", func(t *testing.T) { // Synapse no longer allows downloads over the unauthenticated media endpoints by default runtime.SkipIf(t, runtime.Synapse) + // Venator is too young for any media to be available over the unauthenticated API, + // and always returns M_NOT_FOUND + runtime.SkipIf(t, runtime.Venator) t.Parallel() mxcUri := alice.UploadContent(t, data.MatrixPng, unicodeFileName, "image/png") @@ -144,6 +153,9 @@ func TestMediaFilenames(t *testing.T) { t.Run("Can download with Unicode file name locally", func(t *testing.T) { // Synapse no longer allows downloads over the unauthenticated media endpoints by default runtime.SkipIf(t, runtime.Synapse) + // Venator is too young for any media to be available over the unauthenticated API, + // and always returns M_NOT_FOUND + runtime.SkipIf(t, runtime.Venator) t.Parallel() mxcUri := alice.UploadContent(t, data.MatrixPng, unicodeFileName, "image/png") @@ -171,6 +183,9 @@ func TestMediaFilenames(t *testing.T) { t.Run("Can download with Unicode file name over federation", func(t *testing.T) { // Synapse no longer allows downloads over the unauthenticated media endpoints by default runtime.SkipIf(t, runtime.Synapse) + // Venator is too young for any media to be available over the unauthenticated API, + // and always returns M_NOT_FOUND. It also doesn't implement federation yet. + runtime.SkipIf(t, runtime.Venator) t.Parallel() mxcUri := alice.UploadContent(t, data.MatrixPng, unicodeFileName, "image/png") @@ -183,6 +198,8 @@ func TestMediaFilenames(t *testing.T) { }) t.Run("Can download with Unicode file name over federation via _matrix/client/v1/media/download", func(t *testing.T) { + // Venator: doesn't implement federation yet. + runtime.SkipIf(t, runtime.Venator) t.Parallel() mxcUri := alice.UploadContent(t, data.MatrixPng, unicodeFileName, "image/png") @@ -195,13 +212,11 @@ func TestMediaFilenames(t *testing.T) { }) t.Run("Will serve safe media types as inline", func(t *testing.T) { - if runtime.Homeserver != runtime.Conduwuit { - // We need to check that this security behaviour is being correctly run in - // conduwuit, but since this is not part of the Matrix spec we do not assume - // other homeservers are doing so. - // Skip Synapse because it no longer allows downloads over the unauthenticated media endpoints by default - t.Skip("Skipping test of Content-Disposition header requirements on non-conduwuit homeserver") - } + // We need to check that this security behaviour is being correctly run in + // conduwuit, but since this is not part of the Matrix spec we do not assume + // other homeservers are doing so. + // Skip Synapse because it no longer allows downloads over the unauthenticated media endpoints by default + runtime.SkipUnless(t, runtime.Conduwuit) t.Parallel() mxcUri := alice.UploadContent(t, data.MatrixPng, "", "image/png") @@ -214,12 +229,10 @@ func TestMediaFilenames(t *testing.T) { }) t.Run("Will serve safe media types as inline via _matrix/client/v1/media/download", func(t *testing.T) { - if runtime.Homeserver != runtime.Synapse && runtime.Homeserver != runtime.Conduwuit { - // We need to check that this security behaviour is being correctly run in - // Synapse or conduwuit, but since this is not part of the Matrix spec we do not assume - // other homeservers are doing so. - t.Skip("Skipping test of Content-Disposition header requirements on non-Synapse and non-conduwuit homeserver") - } + // We need to check that this security behaviour is being correctly run in + // Synapse or conduwuit, but since this is not part of the Matrix spec we do not assume + // other homeservers are doing so. + runtime.SkipUnless(t, runtime.Synapse, runtime.Conduwuit) t.Parallel() mxcUri := alice.UploadContent(t, data.MatrixPng, "", "image/png") @@ -232,13 +245,11 @@ func TestMediaFilenames(t *testing.T) { }) t.Run("Will serve safe media types with parameters as inline", func(t *testing.T) { - if runtime.Homeserver != runtime.Conduwuit { - // We need to check that this security behaviour is being correctly run in - // conduwuit, but since this is not part of the Matrix spec we do not assume - // other homeservers are doing so. - // Skip Synapse because it no longer allows downloads over the unauthenticated media endpoints by default - t.Skip("Skipping test of Content-Disposition header requirements on non-conduwuit homeserver") - } + // We need to check that this security behaviour is being correctly run in + // conduwuit, but since this is not part of the Matrix spec we do not assume + // other homeservers are doing so. + // Skip Synapse because it no longer allows downloads over the unauthenticated media endpoints by default + runtime.SkipUnless(t, runtime.Conduwuit) t.Parallel() // Add parameters and upper-case, which should be parsed as text/plain. @@ -252,12 +263,10 @@ func TestMediaFilenames(t *testing.T) { }) t.Run("Will serve safe media types with parameters as inline via _matrix/client/v1/media/download", func(t *testing.T) { - if runtime.Homeserver != runtime.Synapse && runtime.Homeserver != runtime.Conduwuit { - // We need to check that this security behaviour is being correctly run in - // Synapse or conduwuit, but since this is not part of the Matrix spec we do not assume - // other homeservers are doing so. - t.Skip("Skipping test of Content-Disposition header requirements on non-Synapse and non-conduwuit homeserver") - } + // We need to check that this security behaviour is being correctly run in + // Synapse or conduwuit, but since this is not part of the Matrix spec we do not assume + // other homeservers are doing so. + runtime.SkipUnless(t, runtime.Synapse, runtime.Conduwuit) t.Parallel() // Add parameters and upper-case, which should be parsed as text/plain. @@ -271,13 +280,11 @@ func TestMediaFilenames(t *testing.T) { }) t.Run("Will serve unsafe media types as attachments", func(t *testing.T) { - if runtime.Homeserver != runtime.Conduwuit { - // We need to check that this security behaviour is being correctly run in - // conduwuit, but since this is not part of the Matrix spec we do not assume - // other homeservers are doing so. - // Skip Synapse because it no longer allows downloads over the unauthenticated media endpoints by default - t.Skip("Skipping test of Content-Disposition header requirements on non-conduwuit homeserver") - } + // We need to check that this security behaviour is being correctly run in + // conduwuit, but since this is not part of the Matrix spec we do not assume + // other homeservers are doing so. + // Skip Synapse because it no longer allows downloads over the unauthenticated media endpoints by default + runtime.SkipUnless(t, runtime.Conduwuit) t.Parallel() mxcUri := alice.UploadContent(t, data.MatrixSvg, "", "image/svg") @@ -290,12 +297,10 @@ func TestMediaFilenames(t *testing.T) { }) t.Run("Will serve unsafe media types as attachments via _matrix/client/v1/media/download", func(t *testing.T) { - if runtime.Homeserver != runtime.Synapse && runtime.Homeserver != runtime.Conduwuit { - // We need to check that this security behaviour is being correctly run in - // Synapse or conduwuit, but since this is not part of the Matrix spec we do not assume - // other homeservers are doing so. - t.Skip("Skipping test of Content-Disposition header requirements on non-Synapse and non-conduwuit homeserver") - } + // We need to check that this security behaviour is being correctly run in + // Synapse or conduwuit, but since this is not part of the Matrix spec we do not assume + // other homeservers are doing so. + runtime.SkipUnless(t, runtime.Synapse, runtime.Conduwuit) t.Parallel() mxcUri := alice.UploadContent(t, data.MatrixSvg, "", "image/svg") diff --git a/tests/media_nofilename_test.go b/tests/media_nofilename_test.go index d72791af5..5c057439a 100644 --- a/tests/media_nofilename_test.go +++ b/tests/media_nofilename_test.go @@ -17,6 +17,8 @@ import ( func TestMediaWithoutFileName(t *testing.T) { // Synapse no longer allows downloads over the unauthenticated media endpoints by default runtime.SkipIf(t, runtime.Synapse) + // Venator is too young for any media to be available over the unauthenticated API, and always returns M_NOT_FOUND + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) @@ -103,6 +105,8 @@ func TestMediaWithoutFileName(t *testing.T) { // same test as above, but for the new _matrix/client/v1/media endpoint func TestMediaWithoutFileNameCSMediaV1(t *testing.T) { + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 2) defer deployment.Destroy(t) diff --git a/tests/media_thumbnail_test.go b/tests/media_thumbnail_test.go index dd499f189..2f1a30c21 100644 --- a/tests/media_thumbnail_test.go +++ b/tests/media_thumbnail_test.go @@ -39,6 +39,9 @@ func TestLocalPngThumbnail(t *testing.T) { t.Run("test /_matrix/media/v3 endpoint", func(t *testing.T) { // Synapse no longer allows downloads over the unauthenticated media endpoints by default runtime.SkipIf(t, runtime.Synapse) + // Venator is too young for any media to be available over the unauthenticated API, + // and always returns M_NOT_FOUND + runtime.SkipIf(t, runtime.Venator) fetchAndValidateThumbnail(t, alice, uri, false) }) @@ -51,6 +54,8 @@ func TestLocalPngThumbnail(t *testing.T) { // sytest: Remote media can be thumbnailed func TestRemotePngThumbnail(t *testing.T) { + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 2) defer deployment.Destroy(t) @@ -84,6 +89,8 @@ func TestRemotePngThumbnail(t *testing.T) { func TestFederationThumbnail(t *testing.T) { runtime.SkipIf(t, runtime.Dendrite) + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) diff --git a/tests/restricted_room_hierarchy_test.go b/tests/restricted_room_hierarchy_test.go index 32ad0f0ad..a9d80eeda 100644 --- a/tests/restricted_room_hierarchy_test.go +++ b/tests/restricted_room_hierarchy_test.go @@ -5,6 +5,7 @@ package tests import ( "testing" + "github.com/matrix-org/complement/runtime" "github.com/tidwall/gjson" "github.com/matrix-org/complement" @@ -121,6 +122,8 @@ func TestRestrictedRoomsSpacesSummaryLocal(t *testing.T) { // different homeservers, and one might not have the proper information needed to // decide if a user is in a room. func TestRestrictedRoomsSpacesSummaryFederation(t *testing.T) { + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 2) defer deployment.Destroy(t) diff --git a/tests/restricted_rooms_test.go b/tests/restricted_rooms_test.go index efcd7e7ba..b72b630c0 100644 --- a/tests/restricted_rooms_test.go +++ b/tests/restricted_rooms_test.go @@ -187,6 +187,8 @@ func checkRestrictedRoom(t *testing.T, deployment complement.Deployment, alice * // Test joining a room with join rules restricted to membership in another room. func TestRestrictedRoomsLocalJoin(t *testing.T) { + // Venator: does not support room version 8 (>=v10 only) + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) @@ -218,11 +220,15 @@ func TestRestrictedRoomsRemoteJoin(t *testing.T) { // A server will do a remote join for a local user if it is unable to to issue // joins in a restricted room it is already participating in. func TestRestrictedRoomsRemoteJoinLocalUser(t *testing.T) { + // Venator: does not support room version 8 (>=v10 only) + runtime.SkipIf(t, runtime.Venator) doTestRestrictedRoomsRemoteJoinLocalUser(t, "8", "restricted") } func doTestRestrictedRoomsRemoteJoinLocalUser(t *testing.T, roomVersion string, joinRule string) { runtime.SkipIf(t, runtime.Dendrite) // FIXME: https://github.com/matrix-org/dendrite/issues/2801 + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 2) defer deployment.Destroy(t) @@ -331,6 +337,8 @@ func TestRestrictedRoomsRemoteJoinFailOver(t *testing.T) { func doTestRestrictedRoomsRemoteJoinFailOver(t *testing.T, roomVersion string, joinRule string) { runtime.SkipIf(t, runtime.Dendrite) // FIXME: https://github.com/matrix-org/dendrite/issues/2801 + // Venator: does not yet implement federation. + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 3) defer deployment.Destroy(t) @@ -469,6 +477,8 @@ func TestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevelsV11(t *testing.T) { } func doTestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t *testing.T, roomVersion string, joinRule string) { + // Venator: does not yet implement federation. + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 2) defer deployment.Destroy(t) // Create the room diff --git a/tests/room_hierarchy_test.go b/tests/room_hierarchy_test.go index 0d58fcbf2..4980ba8a1 100644 --- a/tests/room_hierarchy_test.go +++ b/tests/room_hierarchy_test.go @@ -18,6 +18,7 @@ import ( "net/url" "testing" + "github.com/matrix-org/complement/runtime" "github.com/tidwall/gjson" "github.com/matrix-org/complement" @@ -544,6 +545,8 @@ func TestClientSpacesSummaryJoinRules(t *testing.T) { // Tests that: // - Querying from root returns the entire graph func TestFederatedClientSpaces(t *testing.T) { + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 2) defer deployment.Destroy(t) diff --git a/tests/room_timestamp_to_event_test.go b/tests/room_timestamp_to_event_test.go index fbc83e225..028315cc6 100644 --- a/tests/room_timestamp_to_event_test.go +++ b/tests/room_timestamp_to_event_test.go @@ -21,12 +21,15 @@ import ( "github.com/matrix-org/complement/helpers" "github.com/matrix-org/complement/match" "github.com/matrix-org/complement/must" + "github.com/matrix-org/complement/runtime" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/tidwall/gjson" "golang.org/x/exp/slices" ) func TestJumpToDateEndpoint(t *testing.T) { + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) deployment := complement.OldDeploy(t, b.BlueprintHSWithApplicationService) defer deployment.Destroy(t) diff --git a/tests/unknown_endpoints_test.go b/tests/unknown_endpoints_test.go index 21761a37c..f45701e33 100644 --- a/tests/unknown_endpoints_test.go +++ b/tests/unknown_endpoints_test.go @@ -9,6 +9,7 @@ import ( "github.com/matrix-org/complement/helpers" "github.com/matrix-org/complement/match" "github.com/matrix-org/complement/must" + "github.com/matrix-org/complement/runtime" ) func queryUnknownEndpoint(t *testing.T, user *client.CSAPI, paths []string) { @@ -73,7 +74,11 @@ func TestUnknownEndpoints(t *testing.T) { // v3 should exist, but not v3/unknown. queryUnknownEndpoint(t, alice, []string{"_matrix", "key", "v2", "unknown"}) - queryUnknownMethod(t, alice, "PUT", []string{"_matrix", "key", "v2", "query"}) + if runtime.Homeserver != runtime.Venator { + // Venator does not implement this endpoint at all - since it doesn't know what the expected methods are, + // it returns 404 M_UNRECOGNIZED, instead of 405 M_UNRECOGNIZED, which causes this to fail. + queryUnknownMethod(t, alice, "PUT", []string{"_matrix", "key", "v2", "query"}) + } }) // Unknown media endpoints. diff --git a/tests/v12_test.go b/tests/v12_test.go index 6139bc6f3..611783d04 100644 --- a/tests/v12_test.go +++ b/tests/v12_test.go @@ -53,6 +53,9 @@ func TestMSC4289PrivilegedRoomCreators(t *testing.T) { } t.Run("PL event is missing creator in users map", func(t *testing.T) { + // Venator: default values (including empty) for power levels are skipped during marshalling, so this test + // flakes as the empty users object is not present (which is legal) + runtime.SkipIf(t, runtime.Venator) roomID := alice.MustCreateRoom(t, map[string]interface{}{ "room_version": roomVersion12, }) @@ -136,6 +139,7 @@ func TestMSC4289PrivilegedRoomCreators(t *testing.T) { // technically not a MSC4289 thing but implementations may set the creator PL to be // above the value expressible in canonical JSON to implement "infinite". t.Run("power level cannot be set beyond max canonical JSON int", func(t *testing.T) { + runtime.SkipIf(t, runtime.Venator) roomID := alice.MustCreateRoom(t, map[string]interface{}{ "room_version": roomVersion12, "preset": "public_chat", @@ -183,6 +187,8 @@ func TestMSC4289PrivilegedRoomCreators(t *testing.T) { }) }) t.Run("admin with >PL100 sorts after the room creator for state resolution", func(t *testing.T) { + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) srv := federation.NewServer(t, deployment, federation.HandleKeyRequests(), federation.HandleMakeSendJoinRequests(), @@ -615,6 +621,8 @@ func TestMSC4291RoomIDAsHashOfCreateEvent(t *testing.T) { } func TestComplementCanCreateValidV12Rooms(t *testing.T) { + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{}) @@ -645,6 +653,8 @@ func TestComplementCanCreateValidV12Rooms(t *testing.T) { } func TestMSC4291RoomIDAsHashOfCreateEvent_AuthEventsOmitsCreateEvent(t *testing.T) { + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{}) @@ -915,6 +925,8 @@ func assertCreateEventIsRoomID(t ct.TestLike, client *client.CSAPI, roomID strin // in other words we apply state resolution to (Alice leave, 250th Charlie display name change). func TestMSC4297StateResolutionV2_1_starts_from_empty_set(t *testing.T) { runtime.SkipIf(t, runtime.Dendrite) // needs additional fixes + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) srv := federation.NewServer(t, deployment, @@ -1096,6 +1108,8 @@ func TestMSC4297StateResolutionV2_1_starts_from_empty_set(t *testing.T) { func TestMSC4297StateResolutionV2_1_includes_conflicted_subgraph(t *testing.T) { runtime.SkipIf(t, runtime.Dendrite) // needs additional fixes + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) srv := federation.NewServer(t, deployment, @@ -1340,6 +1354,8 @@ func asEventIDs(pdus []gomatrixserverlib.PDU) []string { func TestMSC4311FullCreateEventOnStrippedState(t *testing.T) { runtime.SkipIf(t, runtime.Dendrite) // does not implement it yet + // Venator: does not yet implement federation + runtime.SkipIf(t, runtime.Venator) deployment := complement.Deploy(t, 2) defer deployment.Destroy(t) alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{LocalpartSuffix: "alice"}) From b6dbb972c99e05c1ebc63d21a27a65b5b53ceb06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 10:54:21 +0000 Subject: [PATCH 18/18] Bump actions/setup-go from 6.5.0 to 7.0.0 (#911) Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 12200c8a2..4b118f05e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod - name: "Run internal Complement tests" @@ -68,7 +68,7 @@ jobs: with: persist-credentials: false - - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod