Skip to content

Commit abd7264

Browse files
committed
Merge branch 'main' into kegan/client-api
2 parents 57edac7 + 5daf877 commit abd7264

57 files changed

Lines changed: 1132 additions & 422 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ONBOARDING.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,6 @@ To conditionally skip an entire *file* based on the homeserver being run, add a
185185
```go
186186
// +build !dendrite_blacklist
187187
```
188-
You can also do this based on features for MSC tests (which means you must run Complement *with* this tag for these tests *to run*):
189-
```go
190-
// +build msc_2836
191-
```
192188
See [GH Actions](https://github.com/matrix-org/complement/blob/master/.github/workflows/ci.yaml) for an example of how this is used for different homeservers in practice.
193189

194190
### Why do we use `t.Errorf` sometimes and `t.Fatalf` other times?
@@ -210,11 +206,11 @@ For Goland:
210206
* Under "Run"->"Edit Configurations..."->"Edit Configuration Templates..."->"Go Test", and add `COMPLEMENT_BASE_IMAGE=complement-dendrite:latest` to "Environment"
211207
* Then you can right-click on any test file or test case and "Run <test name>".
212208

213-
209+
214210
### How do I make the linter checks pass?
215211

216212
Use [`goimports`](https://pkg.go.dev/golang.org/x/tools/cmd/goimports) to sort imports and format in the style of `gofmt`.
217-
213+
218214
Set this up to run on save in VSCode as follows:
219215
- File -> Preferences -> Settings.
220216
- Search for "Format On Save" and enable it.

README.md

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,24 +140,87 @@ for an example of how to do this correctly.
140140

141141
To get started developing Complement tests, see [the onboarding documentation](ONBOARDING.md).
142142

143-
### Build tags
143+
### Build tags (test blacklisting)
144+
145+
Complement uses build tags to exclude tests for each homeserver implementation.
146+
Build tags are comments at the top of the file that look like:
144147

145-
Complement uses build tags to include or exclude tests for each homeserver. Build tags are comments at the top of the file that look
146-
like:
147148
```go
148-
// +build msc2403
149+
// +build !dendrite_blacklist
149150
```
150-
We have tags for MSCs (the above is in `msc2403_test.go`) as well as general blacklists for a homeserver implementation e.g Dendrite,
151-
which has the name `dendrite_blacklist`. These are implemented as inverted tags such that specifying the tag results in the file not
152-
being picked up by `go test`. For example, `apidoc_presence_test.go` has:
151+
152+
These are implemented as inverted tags, such that specifying the tag results in
153+
the file not being picked up by `go test`. This serves as a way to exclude
154+
known-broken tests per implementation.
155+
156+
For example, `apidoc_presence_test.go` has:
157+
153158
```go
154159
// +build !dendrite_blacklist
155160
```
156-
and all Dendrite tests run with `-tags="dendrite_blacklist"` to cause this file to be skipped. You can run tests with build tags like this:
161+
162+
and all Dendrite tests run with `-tags="dendrite_blacklist"` to cause this file
163+
to be skipped. You can run tests with build tags like this:
164+
157165
```
158-
COMPLEMENT_BASE_IMAGE=complement-synapse:latest go test -v -tags="synapse_blacklist,msc2403" ./tests/...
166+
COMPLEMENT_BASE_IMAGE=complement-synapse:latest go test -v -tags="synapse_blacklist" ./tests/...
159167
```
160-
This runs Complement with a Synapse HS and ignores tests which Synapse doesn't implement, and includes tests for MSC2403.
168+
169+
This runs Complement with a Synapse HS and ignores tests which Synapse doesn't implement.
170+
171+
The currently known blacklist tags are:
172+
173+
* `synapse_blacklist`
174+
* `dendrite_blacklist`
175+
* `conduit_blacklist`
176+
* `conduwuit_blacklist`
177+
178+
### Writing tests for unstable MSCs
179+
180+
Complement is frequently used to test homeserver implementations of unstable
181+
MSCs. As these features/changes often become stable eventually and for
182+
convenience, this repo accepts such tests.
183+
184+
Tests for a given MSC should be placed in a new directory under `tests/`. For
185+
example, to write tests for MSC9999, create a directory at `tests/msc9999`.
186+
187+
This creates a new go "package", and tests contained within will not be run
188+
unless explicitly noted. A package directory should contain the following
189+
files:
190+
191+
```
192+
tests/msc9999
193+
├── main_test.go
194+
└── msc9999_test.go
195+
```
196+
197+
where `main_test.go` sets up Complement and indicates that this is a package
198+
containing tests:
199+
200+
```go
201+
package tests
202+
203+
import (
204+
"testing"
205+
206+
"github.com/matrix-org/complement"
207+
)
208+
209+
func TestMain(m *testing.M) {
210+
complement.TestMain(m, "msc9999")
211+
}
212+
```
213+
214+
and `msc9999_test.go` contains your actual tests. See existing `tests/msc*`
215+
directories for examples.
216+
217+
You can create additional files to separate and organise logical chunks of
218+
tests. Just be sure each file is named `*_test.go` for `go test` to find it.
219+
220+
Once an MSC is accepted, the tests should be migrated out of the `msc*`
221+
directory, as the MSC is now considered stable. Consider adding the tests to
222+
the blacklist of other homeserver implementations (see above section) if they
223+
don't yet implement the new changes described by the MSC.
161224

162225
## Why 'Complement'?
163226

client/client.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"time"
1919

2020
"github.com/matrix-org/gomatrixserverlib"
21+
"github.com/matrix-org/gomatrixserverlib/spec"
2122
"github.com/tidwall/gjson"
2223
"golang.org/x/crypto/curve25519"
2324

@@ -139,7 +140,11 @@ func (c *CSAPI) CreateRoom(t ct.TestLike, body map[string]interface{}) *http.Res
139140
}
140141

141142
// MustJoinRoom joins the room ID or alias given, else fails the test. Returns the room ID.
142-
func (c *CSAPI) MustJoinRoom(t ct.TestLike, roomIDOrAlias string, serverNames []string) string {
143+
//
144+
// Args:
145+
// - `serverNames`: The list of servers to attempt to join the room through.
146+
// These should be a resolvable addresses within the deployment network.
147+
func (c *CSAPI) MustJoinRoom(t ct.TestLike, roomIDOrAlias string, serverNames []spec.ServerName) string {
143148
t.Helper()
144149
res := c.JoinRoom(t, roomIDOrAlias, serverNames)
145150
mustRespond2xx(t, res)
@@ -153,12 +158,19 @@ func (c *CSAPI) MustJoinRoom(t ct.TestLike, roomIDOrAlias string, serverNames []
153158
}
154159

155160
// JoinRoom joins the room ID or alias given. Returns the raw http response
156-
func (c *CSAPI) JoinRoom(t ct.TestLike, roomIDOrAlias string, serverNames []string) *http.Response {
161+
//
162+
// Args:
163+
// - `serverNames`: The list of servers to attempt to join the room through.
164+
// These should be a resolvable addresses within the deployment network.
165+
func (c *CSAPI) JoinRoom(t ct.TestLike, roomIDOrAlias string, serverNames []spec.ServerName) *http.Response {
157166
t.Helper()
158167
// construct URL query parameters
159-
query := make(url.Values, len(serverNames))
160-
for _, serverName := range serverNames {
161-
query.Add("server_name", serverName)
168+
serverNameStrings := make([]string, len(serverNames))
169+
for i, serverName := range serverNames {
170+
serverNameStrings[i] = string(serverName)
171+
}
172+
query := url.Values{
173+
"server_name": serverNameStrings,
162174
}
163175
// join the room
164176
return c.Do(

cmd/perftest/snapshot.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"encoding/json"
66
"time"
77

8-
"github.com/docker/docker/api/types"
8+
"github.com/docker/docker/api/types/container"
99
"github.com/matrix-org/complement/internal/docker"
1010
)
1111

@@ -30,7 +30,7 @@ func snapshotStats(spanName, desc string, deployment *docker.Deployment, absDura
3030
if err != nil {
3131
return nil
3232
}
33-
var sj types.StatsJSON
33+
var sj container.StatsResponse
3434
err = json.NewDecoder(stats.Body).Decode(&sj)
3535
stats.Body.Close()
3636
if err != nil {

config/config.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@ type Complement struct {
106106
// disable this behaviour being added later, once this has stablised.
107107
EnableDirtyRuns bool
108108

109+
// The IP that is used to connect to the running homeserver from the host.
110+
//
111+
// For Complement tests, this is always configured as `127.0.0.1` but can be
112+
// overridden by homerunner to allow binding to a different IP address
113+
// (`HOMERUNNER_HS_PORTBINDING_IP`).
114+
//
115+
// This field is used for the host-accessible homeserver URLs (as the hostname)
116+
// so clients in your tests can access the homeserver.
109117
HSPortBindingIP string
110118

111119
// Name: COMPLEMENT_POST_TEST_SCRIPT

federation/handle.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func MakeRespMakeKnock(s *Server, room *ServerRoom, userID string) (resp fclient
117117
// the current server is returned to the joining server.
118118
func SendJoinRequestsHandler(s *Server, w http.ResponseWriter, req *http.Request, expectPartialState bool, omitServersInRoom bool) {
119119
fedReq, errResp := fclient.VerifyHTTPRequest(
120-
req, time.Now(), spec.ServerName(s.serverName), nil, s.keyRing,
120+
req, time.Now(), s.serverName, nil, s.keyRing,
121121
)
122122
if fedReq == nil {
123123
w.WriteHeader(errResp.Code)
@@ -208,7 +208,7 @@ func HandleInviteRequests(inviteCallback func(gomatrixserverlib.PDU)) func(*Serv
208208
// https://matrix.org/docs/spec/server_server/r0.1.4#put-matrix-federation-v2-invite-roomid-eventid
209209
s.mux.Handle("/_matrix/federation/v2/invite/{roomID}/{eventID}", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
210210
fedReq, errResp := fclient.VerifyHTTPRequest(
211-
req, time.Now(), spec.ServerName(s.serverName), nil, s.keyRing,
211+
req, time.Now(), s.serverName, nil, s.keyRing,
212212
)
213213
if fedReq == nil {
214214
w.WriteHeader(errResp.Code)
@@ -236,7 +236,7 @@ func HandleInviteRequests(inviteCallback func(gomatrixserverlib.PDU)) func(*Serv
236236
}
237237

238238
// Sign the event before we send it back
239-
signedEvent := inviteRequest.Event().Sign(s.serverName, s.KeyID, s.Priv)
239+
signedEvent := inviteRequest.Event().Sign(string(s.serverName), s.KeyID, s.Priv)
240240

241241
// Send the response
242242
res := map[string]interface{}{
@@ -263,7 +263,7 @@ func HandleDirectoryLookups() func(*Server) {
263263
b, err := json.Marshal(fclient.RespDirectory{
264264
RoomID: roomID,
265265
Servers: []spec.ServerName{
266-
spec.ServerName(s.serverName),
266+
s.serverName,
267267
},
268268
})
269269
if err != nil {
@@ -432,7 +432,7 @@ func HandleMediaRequests(mediaIds map[string]func(w http.ResponseWriter)) func(*
432432
origin := vars["origin"]
433433
mediaId := vars["mediaId"]
434434

435-
if origin != srv.serverName {
435+
if origin != string(srv.serverName) {
436436
w.WriteHeader(400)
437437
w.Write([]byte("complement: Invalid Origin; Expected " + srv.serverName))
438438
return
@@ -471,7 +471,7 @@ func HandleTransactionRequests(pduCallback func(gomatrixserverlib.PDU), eduCallb
471471

472472
// Check federation signature
473473
fedReq, errResp := fclient.VerifyHTTPRequest(
474-
req, time.Now(), spec.ServerName(srv.serverName), nil, srv.keyRing,
474+
req, time.Now(), srv.serverName, nil, srv.keyRing,
475475
)
476476
if fedReq == nil {
477477
log.Printf(

federation/server.go

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@ type Server struct {
4848
// Default: true
4949
UnexpectedRequestsAreErrors bool
5050

51-
Priv ed25519.PrivateKey
52-
KeyID gomatrixserverlib.KeyID
53-
serverName string
51+
Priv ed25519.PrivateKey
52+
KeyID gomatrixserverlib.KeyID
53+
// The homeserver name. This should be a resolvable address in the deployment network
54+
serverName spec.ServerName
5455
listening bool
5556

5657
certPath string
@@ -80,7 +81,7 @@ func NewServer(t ct.TestLike, deployment FederationDeployment, opts ...func(*Ser
8081
mux: mux.NewRouter(),
8182
// The server name will be updated when the caller calls Listen() to include the port number
8283
// of the HTTP server e.g "host.docker.internal:56353"
83-
serverName: deployment.GetConfig().HostnameRunningComplement,
84+
serverName: spec.ServerName(deployment.GetConfig().HostnameRunningComplement),
8485
rooms: make(map[string]*ServerRoom),
8586
aliases: make(map[string]string),
8687
UnexpectedRequestsAreErrors: true,
@@ -142,7 +143,7 @@ func NewServer(t ct.TestLike, deployment FederationDeployment, opts ...func(*Ser
142143
// It is not supported to call ServerName() before Listen() because Listen() modifies the server name.
143144
// Listen() will select a random OS-provided high-numbered port to listen on, which then needs to be
144145
// retrofitted into the server name so containers know how to route to it.
145-
func (s *Server) ServerName() string {
146+
func (s *Server) ServerName() spec.ServerName {
146147
if !s.listening {
147148
ct.Fatalf(s.t, "ServerName() called before Listen() - this is not supported because Listen() chooses a high-numbered port and thus changes the server name. Ensure you Listen() first!")
148149
}
@@ -205,28 +206,31 @@ func (s *Server) FederationClient(deployment FederationDeployment) fclient.Feder
205206
ct.Fatalf(s.t, "FederationClient() called before Listen() - this is not supported because Listen() chooses a high-numbered port and thus changes the server name and thus changes the way federation requests are signed. Ensure you Listen() first!")
206207
}
207208
identity := fclient.SigningIdentity{
208-
ServerName: spec.ServerName(s.ServerName()),
209+
ServerName: s.ServerName(),
209210
KeyID: s.KeyID,
210211
PrivateKey: s.Priv,
211212
}
212-
f := fclient.NewFederationClient(
213+
fedClient := fclient.NewFederationClient(
213214
[]*fclient.SigningIdentity{&identity},
214215
fclient.WithTransport(deployment.RoundTripper()),
215216
)
216-
return f
217+
return fedClient
217218
}
218219

219220
// MustSendTransaction sends the given PDUs/EDUs to the target destination, returning an error if the /send fails or if the response contains an error
220221
// for any sent PDUs. Times out after 10 seconds.
221-
func (s *Server) MustSendTransaction(t ct.TestLike, deployment FederationDeployment, destination string, pdus []json.RawMessage, edus []gomatrixserverlib.EDU) {
222+
//
223+
// Args:
224+
// - `destination`: This should be a resolvable addresses within the deployment network.
225+
func (s *Server) MustSendTransaction(t ct.TestLike, deployment FederationDeployment, destination spec.ServerName, pdus []json.RawMessage, edus []gomatrixserverlib.EDU) {
222226
t.Helper()
223-
cli := s.FederationClient(deployment)
227+
fedClient := s.FederationClient(deployment)
224228
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
225229
defer cancel()
226-
resp, err := cli.SendTransaction(ctx, gomatrixserverlib.Transaction{
230+
resp, err := fedClient.SendTransaction(ctx, gomatrixserverlib.Transaction{
227231
TransactionID: gomatrixserverlib.TransactionID(fmt.Sprintf("complement-%d", time.Now().Nanosecond())),
228232
Origin: spec.ServerName(s.ServerName()),
229-
Destination: spec.ServerName(destination),
233+
Destination: destination,
230234
PDUs: pdus,
231235
EDUs: edus,
232236
})
@@ -319,6 +323,9 @@ func (s *Server) MustCreateEvent(t ct.TestLike, room *ServerRoom, ev Event) goma
319323

320324
// MustJoinRoom will make the server send a make_join and a send_join to join a room
321325
// It returns the resultant room.
326+
//
327+
// Args:
328+
// - `remoteServer`: This should be a resolvable addresses within the deployment network.
322329
func (s *Server) MustJoinRoom(t ct.TestLike, deployment FederationDeployment, remoteServer spec.ServerName, roomID string, userID string, opts ...JoinRoomOpt) *ServerRoom {
323330
t.Helper()
324331
var jr joinRoom
@@ -401,6 +408,9 @@ func (s *Server) MustJoinRoom(t ct.TestLike, deployment FederationDeployment, re
401408
}
402409

403410
// Leaves a room. If this is rejecting an invite then a make_leave request is made first, before send_leave.
411+
//
412+
// Args:
413+
// - `remoteServer`: This should be a resolvable addresses within the deployment network.
404414
func (s *Server) MustLeaveRoom(t ct.TestLike, deployment FederationDeployment, remoteServer spec.ServerName, roomID string, userID string) {
405415
t.Helper()
406416
origin := spec.ServerName(s.serverName)
@@ -449,7 +459,7 @@ func (s *Server) ValidFederationRequest(t ct.TestLike, handler func(fr *fclient.
449459
return func(w http.ResponseWriter, req *http.Request) {
450460
// Check federation signature
451461
fedReq, errResp := fclient.VerifyHTTPRequest(
452-
req, time.Now(), spec.ServerName(s.serverName), nil, s.keyRing,
462+
req, time.Now(), s.serverName, nil, s.keyRing,
453463
)
454464
if fedReq == nil {
455465
ct.Errorf(t,
@@ -495,7 +505,7 @@ func (s *Server) Listen() (cancel func()) {
495505
ct.Fatalf(s.t, "ListenFederationServer: net.Listen failed: %s", err)
496506
}
497507
port := ln.Addr().(*net.TCPAddr).Port
498-
s.serverName += fmt.Sprintf(":%d", port)
508+
s.serverName = spec.ServerName(fmt.Sprintf("%s:%d", s.serverName, port))
499509
s.listening = true
500510

501511
go func() {
@@ -647,7 +657,7 @@ func (f *basicKeyFetcher) FetchKeys(
647657
) {
648658
result := make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, len(requests))
649659
for req := range requests {
650-
if string(req.ServerName) == f.srv.serverName && req.KeyID == f.srv.KeyID {
660+
if req.ServerName == f.srv.serverName && req.KeyID == f.srv.KeyID {
651661
publicKey := f.srv.Priv.Public().(ed25519.PublicKey)
652662
result[req] = gomatrixserverlib.PublicKeyLookupResult{
653663
ValidUntilTS: spec.AsTimestamp(time.Now().Add(24 * time.Hour)),

0 commit comments

Comments
 (0)