Skip to content

Commit

Permalink
Add TestSigkillBeforeKeysUploadResponse
Browse files Browse the repository at this point in the history
This fails but is just logging the failure now rather than
failing CI. Needs a lot of love to make it not awful, but it
works.
  • Loading branch information
kegsay committed Jan 12, 2024
1 parent b13a69f commit f015351
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 81 deletions.
94 changes: 68 additions & 26 deletions internal/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package api

import (
"fmt"
"testing"
"time"

"github.com/matrix-org/complement/client"
Expand All @@ -27,34 +26,35 @@ type Client interface {
// Specifically, we need to shut off existing browsers and any FFI bindings.
// If we get callbacks/events after this point, tests may panic if the callbacks
// log messages.
Close(t *testing.T)

Login(t *testing.T, opts ClientCreationOpts) error
Close(t Test)
// Remove any persistent storage, if it was enabled.
DeletePersistentStorage(t Test)
Login(t Test, opts ClientCreationOpts) error
// StartSyncing to begin syncing from sync v2 / sliding sync.
// Tests should call stopSyncing() at the end of the test.
// MUST BLOCK until the initial sync is complete.
StartSyncing(t *testing.T) (stopSyncing func())
StartSyncing(t Test) (stopSyncing func())
// IsRoomEncrypted returns true if the room is encrypted. May return an error e.g if you
// provide a bogus room ID.
IsRoomEncrypted(t *testing.T, roomID string) (bool, error)
IsRoomEncrypted(t Test, roomID string) (bool, error)
// SendMessage sends the given text as an m.room.message with msgtype:m.text into the given
// room. Returns the event ID of the sent event, so MUST BLOCK until the event has been sent.
SendMessage(t *testing.T, roomID, text string) (eventID string)
SendMessage(t Test, roomID, text string) (eventID string)
// TrySendMessage tries to send the message, but can fail.
TrySendMessage(t *testing.T, roomID, text string) (eventID string, err error)
TrySendMessage(t Test, roomID, text string) (eventID string, err error)
// Wait until an event with the given body is seen. Not all impls expose event IDs
// hence needing to use body as a proxy.
WaitUntilEventInRoom(t *testing.T, roomID string, checker func(e Event) bool) Waiter
WaitUntilEventInRoom(t Test, roomID string, checker func(e Event) bool) Waiter
// Backpaginate in this room by `count` events.
MustBackpaginate(t *testing.T, roomID string, count int)
MustBackpaginate(t Test, roomID string, count int)
// MustGetEvent will return the client's view of this event, or fail the test if the event cannot be found.
MustGetEvent(t *testing.T, roomID, eventID string) Event
MustGetEvent(t Test, roomID, eventID string) Event
// MustBackupKeys will backup E2EE keys, else fail the test.
MustBackupKeys(t *testing.T) (recoveryKey string)
MustBackupKeys(t Test) (recoveryKey string)
// MustLoadBackup will recover E2EE keys from the latest backup, else fail the test.
MustLoadBackup(t *testing.T, recoveryKey string)
MustLoadBackup(t Test, recoveryKey string)
// Log something to stdout and the underlying client log file
Logf(t *testing.T, format string, args ...interface{})
Logf(t Test, format string, args ...interface{})
// The user for this client
UserID() string
Type() ClientTypeLang
Expand All @@ -64,68 +64,80 @@ type LoggedClient struct {
Client
}

func (c *LoggedClient) Close(t *testing.T) {
func (c *LoggedClient) Login(t Test, opts ClientCreationOpts) error {
t.Helper()
c.Logf(t, "%s Login %+v", c.logPrefix(), opts)
return c.Client.Login(t, opts)
}

func (c *LoggedClient) Close(t Test) {
t.Helper()
c.Logf(t, "%s Close", c.logPrefix())
c.Client.Close(t)
}

func (c *LoggedClient) StartSyncing(t *testing.T) (stopSyncing func()) {
func (c *LoggedClient) StartSyncing(t Test) (stopSyncing func()) {
t.Helper()
c.Logf(t, "%s StartSyncing starting to sync", c.logPrefix())
stopSyncing = c.Client.StartSyncing(t)
c.Logf(t, "%s StartSyncing now syncing", c.logPrefix())
return
}

func (c *LoggedClient) IsRoomEncrypted(t *testing.T, roomID string) (bool, error) {
func (c *LoggedClient) IsRoomEncrypted(t Test, roomID string) (bool, error) {
t.Helper()
c.Logf(t, "%s IsRoomEncrypted %s", c.logPrefix(), roomID)
return c.Client.IsRoomEncrypted(t, roomID)
}

func (c *LoggedClient) TrySendMessage(t *testing.T, roomID, text string) (eventID string, err error) {
func (c *LoggedClient) TrySendMessage(t Test, roomID, text string) (eventID string, err error) {
t.Helper()
c.Logf(t, "%s TrySendMessage %s => %s", c.logPrefix(), roomID, text)
eventID, err = c.Client.TrySendMessage(t, roomID, text)
c.Logf(t, "%s TrySendMessage %s => %s", c.logPrefix(), roomID, eventID)
return
}

func (c *LoggedClient) SendMessage(t *testing.T, roomID, text string) (eventID string) {
func (c *LoggedClient) SendMessage(t Test, roomID, text string) (eventID string) {
t.Helper()
c.Logf(t, "%s SendMessage %s => %s", c.logPrefix(), roomID, text)
eventID = c.Client.SendMessage(t, roomID, text)
c.Logf(t, "%s SendMessage %s => %s", c.logPrefix(), roomID, eventID)
return
}

func (c *LoggedClient) WaitUntilEventInRoom(t *testing.T, roomID string, checker func(e Event) bool) Waiter {
func (c *LoggedClient) WaitUntilEventInRoom(t Test, roomID string, checker func(e Event) bool) Waiter {
t.Helper()
c.Logf(t, "%s WaitUntilEventInRoom %s", c.logPrefix(), roomID)
return c.Client.WaitUntilEventInRoom(t, roomID, checker)
}

func (c *LoggedClient) MustBackpaginate(t *testing.T, roomID string, count int) {
func (c *LoggedClient) MustBackpaginate(t Test, roomID string, count int) {
t.Helper()
c.Logf(t, "%s MustBackpaginate %d %s", c.logPrefix(), count, roomID)
c.Client.MustBackpaginate(t, roomID, count)
}

func (c *LoggedClient) MustBackupKeys(t *testing.T) (recoveryKey string) {
func (c *LoggedClient) MustBackupKeys(t Test) (recoveryKey string) {
t.Helper()
c.Logf(t, "%s MustBackupKeys", c.logPrefix())
recoveryKey = c.Client.MustBackupKeys(t)
c.Logf(t, "%s MustBackupKeys => %s", c.logPrefix(), recoveryKey)
return recoveryKey
}

func (c *LoggedClient) MustLoadBackup(t *testing.T, recoveryKey string) {
func (c *LoggedClient) MustLoadBackup(t Test, recoveryKey string) {
t.Helper()
c.Logf(t, "%s MustLoadBackup key=%s", c.logPrefix(), recoveryKey)
c.Client.MustLoadBackup(t, recoveryKey)
}

func (c *LoggedClient) DeletePersistentStorage(t Test) {
t.Helper()
c.Logf(t, "%s DeletePersistentStorage", c.logPrefix())
c.Client.DeletePersistentStorage(t)
}

func (c *LoggedClient) logPrefix() string {
return fmt.Sprintf("[%s](%s)", c.UserID(), c.Type())
}
Expand All @@ -142,6 +154,9 @@ type ClientCreationOpts struct {
// Required. The password for this account.
Password string

// Optional. If true, persistent storage will be used for the same user|device ID.
PersistentStorage bool

// Optional. Set this to login with this device ID.
DeviceID string
}
Expand All @@ -167,7 +182,7 @@ type Event struct {
}

type Waiter interface {
Wait(t *testing.T, s time.Duration)
Wait(t Test, s time.Duration)
}

func CheckEventHasBody(body string) func(e Event) bool {
Expand All @@ -186,15 +201,42 @@ const ansiRedForeground = "\x1b[31m"
const ansiResetForeground = "\x1b[39m"

// Errorf is a wrapper around t.Errorf which prints the failing error message in red.
func Errorf(t *testing.T, format string, args ...any) {
func Errorf(t Test, format string, args ...any) {
t.Helper()
format = ansiRedForeground + format + ansiResetForeground
t.Errorf(format, args...)
}

// Fatalf is a wrapper around t.Fatalf which prints the failing error message in red.
func Fatalf(t *testing.T, format string, args ...any) {
func Fatalf(t Test, format string, args ...any) {
t.Helper()
format = ansiRedForeground + format + ansiResetForeground
t.Fatalf(format, args...)
}

type Test interface {
Logf(f string, args ...any)
Errorf(f string, args ...any)
Fatalf(f string, args ...any)
Helper()
Name() string
}

// TODO move to must package when it accepts an interface

// NotError will ensure `err` is nil else terminate the test with `msg`.
func MustNotError(t Test, msg string, err error) {
t.Helper()
if err != nil {
Fatalf(t, "must.NotError: %s -> %s", msg, err)
}
}

// NotEqual ensures that got!=want else logs an error.
// The 'msg' is displayed with the error to provide extra context.
func MustNotEqual[V comparable](t Test, got, want V, msg string) {
t.Helper()
if got == want {
Errorf(t, "NotEqual %s: got '%v', want '%v'", msg, got, want)
}
}
5 changes: 2 additions & 3 deletions internal/api/js/chrome/chrome.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"net/http"
"strconv"
"sync"
"testing"
"time"

"github.com/chromedp/cdproto/runtime"
Expand All @@ -35,7 +34,7 @@ type Void *runtime.RemoteObject
//
// result, err := RunAsyncFn[string](t, ctx, "return await getSomeString()")
// void, err := RunAsyncFn[chrome.Void](t, ctx, "doSomething(); await doSomethingElse();")
func RunAsyncFn[T any](t *testing.T, ctx context.Context, js string) (*T, error) {
func RunAsyncFn[T any](t api.Test, ctx context.Context, js string) (*T, error) {
t.Helper()
out := new(T)
err := chromedp.Run(ctx,
Expand All @@ -54,7 +53,7 @@ func RunAsyncFn[T any](t *testing.T, ctx context.Context, js string) (*T, error)
// Run an anonymous async iffe in the browser. Set the type parameter to a basic data type
// which can be returned as JSON e.g string, map[string]any, []string. If you do not want
// to return anything, use chrome.Void
func MustRunAsyncFn[T any](t *testing.T, ctx context.Context, js string) *T {
func MustRunAsyncFn[T any](t api.Test, ctx context.Context, js string) *T {
t.Helper()
result, err := RunAsyncFn[T](t, ctx, js)
if err != nil {
Expand Down
37 changes: 20 additions & 17 deletions internal/api/js/js.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import (
"os"
"strings"
"sync/atomic"
"testing"
"time"

"github.com/matrix-org/complement-crypto/internal/api"
"github.com/matrix-org/complement-crypto/internal/api/js/chrome"
"github.com/matrix-org/complement/must"
"github.com/tidwall/gjson"
)

Expand Down Expand Up @@ -47,7 +45,7 @@ type JSClient struct {
userID string
}

func NewJSClient(t *testing.T, opts api.ClientCreationOpts) (api.Client, error) {
func NewJSClient(t api.Test, opts api.ClientCreationOpts) (api.Client, error) {
jsc := &JSClient{
listeners: make(map[int32]func(roomID string, ev api.Event)),
userID: opts.UserID,
Expand Down Expand Up @@ -116,7 +114,7 @@ func NewJSClient(t *testing.T, opts api.ClientCreationOpts) (api.Client, error)
return &api.LoggedClient{Client: jsc}, nil
}

func (c *JSClient) Login(t *testing.T, opts api.ClientCreationOpts) error {
func (c *JSClient) Login(t api.Test, opts api.ClientCreationOpts) error {
deviceID := "undefined"
if opts.DeviceID != "" {
deviceID = `"` + opts.DeviceID + `"`
Expand All @@ -142,11 +140,16 @@ func (c *JSClient) Login(t *testing.T, opts api.ClientCreationOpts) error {
return nil
}

func (c *JSClient) DeletePersistentStorage(t api.Test) {
t.Helper()
// TODO
}

// Close is called to clean up resources.
// Specifically, we need to shut off existing browsers and any FFI bindings.
// If we get callbacks/events after this point, tests may panic if the callbacks
// log messages.
func (c *JSClient) Close(t *testing.T) {
func (c *JSClient) Close(t api.Test) {
c.browser.Cancel()
c.listeners = make(map[int32]func(roomID string, ev api.Event))
}
Expand All @@ -155,7 +158,7 @@ func (c *JSClient) UserID() string {
return c.userID
}

func (c *JSClient) MustGetEvent(t *testing.T, roomID, eventID string) api.Event {
func (c *JSClient) MustGetEvent(t api.Test, roomID, eventID string) api.Event {
t.Helper()
// serialised output (if encrypted):
// {
Expand Down Expand Up @@ -197,7 +200,7 @@ func (c *JSClient) MustGetEvent(t *testing.T, roomID, eventID string) api.Event

// StartSyncing to begin syncing from sync v2 / sliding sync.
// Tests should call stopSyncing() at the end of the test.
func (c *JSClient) StartSyncing(t *testing.T) (stopSyncing func()) {
func (c *JSClient) StartSyncing(t api.Test) (stopSyncing func()) {
t.Helper()
chrome.MustRunAsyncFn[chrome.Void](t, c.browser.Ctx, fmt.Sprintf(`
var fn;
Expand Down Expand Up @@ -234,7 +237,7 @@ func (c *JSClient) StartSyncing(t *testing.T) (stopSyncing func()) {

// IsRoomEncrypted returns true if the room is encrypted. May return an error e.g if you
// provide a bogus room ID.
func (c *JSClient) IsRoomEncrypted(t *testing.T, roomID string) (bool, error) {
func (c *JSClient) IsRoomEncrypted(t api.Test, roomID string) (bool, error) {
t.Helper()
isEncrypted, err := chrome.RunAsyncFn[bool](
t, c.browser.Ctx, fmt.Sprintf(`return window.__client.isRoomEncrypted("%s")`, roomID),
Expand All @@ -247,14 +250,14 @@ func (c *JSClient) IsRoomEncrypted(t *testing.T, roomID string) (bool, error) {

// SendMessage sends the given text as an m.room.message with msgtype:m.text into the given
// room.
func (c *JSClient) SendMessage(t *testing.T, roomID, text string) (eventID string) {
func (c *JSClient) SendMessage(t api.Test, roomID, text string) (eventID string) {
t.Helper()
eventID, err := c.TrySendMessage(t, roomID, text)
must.NotError(t, "failed to sendMessage", err)
api.MustNotError(t, "failed to sendMessage", err)
return eventID
}

func (c *JSClient) TrySendMessage(t *testing.T, roomID, text string) (eventID string, err error) {
func (c *JSClient) TrySendMessage(t api.Test, roomID, text string) (eventID string, err error) {
t.Helper()
res, err := chrome.RunAsyncFn[map[string]interface{}](t, c.browser.Ctx, fmt.Sprintf(`
return await window.__client.sendMessage("%s", {
Expand All @@ -267,14 +270,14 @@ func (c *JSClient) TrySendMessage(t *testing.T, roomID, text string) (eventID st
return (*res)["event_id"].(string), nil
}

func (c *JSClient) MustBackpaginate(t *testing.T, roomID string, count int) {
func (c *JSClient) MustBackpaginate(t api.Test, roomID string, count int) {
t.Helper()
chrome.MustRunAsyncFn[chrome.Void](t, c.browser.Ctx, fmt.Sprintf(
`await window.__client.scrollback(window.__client.getRoom("%s"), %d);`, roomID, count,
))
}

func (c *JSClient) MustBackupKeys(t *testing.T) (recoveryKey string) {
func (c *JSClient) MustBackupKeys(t api.Test) (recoveryKey string) {
t.Helper()
key := chrome.MustRunAsyncFn[string](t, c.browser.Ctx, `
// we need to ensure that we have a recovery key first, though we don't actually care about it..?
Expand All @@ -295,7 +298,7 @@ func (c *JSClient) MustBackupKeys(t *testing.T) (recoveryKey string) {
return *key
}

func (c *JSClient) MustLoadBackup(t *testing.T, recoveryKey string) {
func (c *JSClient) MustLoadBackup(t api.Test, recoveryKey string) {
chrome.MustRunAsyncFn[chrome.Void](t, c.browser.Ctx, fmt.Sprintf(`
// we assume the recovery key is the private key for the default key id so
// figure out what that key id is.
Expand All @@ -312,7 +315,7 @@ func (c *JSClient) MustLoadBackup(t *testing.T, recoveryKey string) {
recoveryKey))
}

func (c *JSClient) WaitUntilEventInRoom(t *testing.T, roomID string, checker func(e api.Event) bool) api.Waiter {
func (c *JSClient) WaitUntilEventInRoom(t api.Test, roomID string, checker func(e api.Event) bool) api.Waiter {
t.Helper()
return &jsTimelineWaiter{
roomID: roomID,
Expand All @@ -321,7 +324,7 @@ func (c *JSClient) WaitUntilEventInRoom(t *testing.T, roomID string, checker fun
}
}

func (c *JSClient) Logf(t *testing.T, format string, args ...interface{}) {
func (c *JSClient) Logf(t api.Test, format string, args ...interface{}) {
t.Helper()
formatted := fmt.Sprintf(t.Name()+": "+format, args...)
chrome.MustRunAsyncFn[chrome.Void](t, c.browser.Ctx, fmt.Sprintf(`console.log("%s");`, formatted))
Expand All @@ -346,7 +349,7 @@ type jsTimelineWaiter struct {
client *JSClient
}

func (w *jsTimelineWaiter) Wait(t *testing.T, s time.Duration) {
func (w *jsTimelineWaiter) Wait(t api.Test, s time.Duration) {
t.Helper()
updates := make(chan bool, 3)
cancel := w.client.listenForUpdates(func(roomID string, ev api.Event) {
Expand Down
Loading

0 comments on commit f015351

Please sign in to comment.