diff --git a/ENVIRONMENT.md b/ENVIRONMENT.md new file mode 100644 index 0000000..7be6f2a --- /dev/null +++ b/ENVIRONMENT.md @@ -0,0 +1,19 @@ +*This file is automatically generated via ./cmd/gendoc* + +## Complement-Crypto Configuration +Complement is configured exclusively through the use of environment variables. These variables are described below. Additional environment variables can be used, and are outlined at https://github.com/matrix-org/complement/blob/main/ENVIRONMENT.md + +#### `COMPLEMENT_CRYPTO_TCPDUMP` +If 1, automatically attempts to run `tcpdump` when the containers are running. Stops dumping when tests complete. This will probably require you to run `go test` with `sudo -E`. The `.pcap` file is written to `tests/test.pcap`. +- Type: `bool` +- Default: 0 + +#### `COMPLEMENT_CRYPTO_TEST_CLIENT_MATRIX` +The client test matrix to run. Every test is run for each given permutation. The default matrix tests all JS/Rust permutations _ignoring federation_. Valid values are: - `j`: Run a JS SDK client on hs1. - `r`: Run a Rust SDK FFI client on hs1. - `J`: Run a JS SDK client on hs2. - `R`: Run a Rust SDK FFI client on hs2. TODO: needs additional SS proxy / postgres. For example, for a simple "Alice and Bob" test: - `rj,rr`: Run the test twice. Run 1: Alice=rust, Bob=JS. Run 2: Alice=rust, Bob=rust. All on HS1. - `jJ`: Run the test once. Run 1: Alice=JS on HS1, Bob=JS on HS2. Tests federation. +- Type: `[][]ClientType` +- Default: jj,jr,rj,rr + +#### `COMPLEMENT_CRYPTO_WRITE_CONTAINER_LOGS` +If 1, writes container logs to ./tests. Useful as a debugging tool. +- Type: `bool` +- Default: 0 diff --git a/README.md b/README.md index 6b7d556..5cb4650 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,19 @@ cargo install uniffi-bindgen-go --path ./uniffi-bindgen-go/bindgen - Sanity check compile `LIBRARY_PATH="$LIBRARY_PATH:/path/to/matrix-rust-sdk/target/debug" go test -c ./tests` +### Architecture + +``` + Host | dockerd + | +----------+ +----------+ + | .--> | ss proxy | <--> | postgres | + +----------+ | +-----------+ | +-----+----+ +----------+ + | Go tests | <--|--> | mitmproxy | <--+--> | hs1 | + +----------+ | +-----------+ | +-----+ + | `--> | hs2 | + | +-----+ +``` + ### Github Action (TODO) Inputs: diff --git a/cmd/gendoc/main.go b/cmd/gendoc/main.go new file mode 100644 index 0000000..2340446 --- /dev/null +++ b/cmd/gendoc/main.go @@ -0,0 +1,132 @@ +package main + +import ( + "flag" + "fmt" + "go/ast" + "go/parser" + "go/token" + "log" + "os" + "sort" + "strings" +) + +var configPath = flag.String("config", "internal/config/config.go", "The path to internal/config/config.go") + +type VarDoc struct { + Name string + Description string + Default string + Type string +} + +func NewVarDoc(docstring string) (vd VarDoc) { + lines := strings.Split(docstring, "\n") + isDescription := false + for _, l := range lines { + if strings.HasPrefix(l, "Name:") { + isDescription = false + vd.Name = strings.TrimSpace(strings.TrimPrefix(l, "Name:")) + } + if strings.HasPrefix(l, "Default:") { + isDescription = false + vd.Default = strings.TrimSpace(strings.TrimPrefix(l, "Default:")) + } + if strings.HasPrefix(l, "Description:") { + l = strings.TrimPrefix(l, "Description:") + isDescription = true + } + if isDescription { + vd.Description += strings.TrimSpace(l) + " " + } + } + return +} + +func findComplementStruct(path string) *ast.StructType { + fset := token.NewFileSet() + node, err := parser.ParseFile(fset, path, nil, parser.ParseComments) + if err != nil { + log.Fatal(err) + } + var complementConfigType *ast.TypeSpec +FindStruct: + for _, d := range node.Decls { + typeNode, ok := d.(*ast.GenDecl) + if !ok || typeNode.Tok != token.TYPE { // we want `type` keywords + continue + } + for _, s := range typeNode.Specs { + typeSpec, ok := s.(*ast.TypeSpec) + if !ok { + continue + } + if typeSpec.Name.Name == "ComplementCrypto" { + complementConfigType = typeSpec + break FindStruct + } + } + } + sType, ok := complementConfigType.Type.(*ast.StructType) + if !ok { + return nil + } + return sType +} + +func typeForExpr(ex ast.Expr) string { + switch typeDecl := ex.(type) { + case *ast.Ident: + return typeDecl.Name + case *ast.SelectorExpr: + return typeDecl.Sel.Name + case *ast.ArrayType: + return "[]" + typeForExpr(typeDecl.Elt) + case *ast.MapType: + return "map[" + typeForExpr(typeDecl.Key) + "]" + typeForExpr(typeDecl.Value) + default: + return "-" + } +} + +func main() { + flag.Parse() + if *configPath == "" { + flag.Usage() + os.Exit(1) + } + complement := findComplementStruct(*configPath) + if complement == nil { + log.Fatal("file does not contain type ComplementCrypto struct {...}") + } + var varDocs []VarDoc + // loop each field looking for valid comments + for _, f := range complement.Fields.List { + fieldComment := f.Doc.Text() + vd := NewVarDoc(fieldComment) + if vd.Name == "" { + continue // not valid comment + } + vd.Type = typeForExpr(f.Type) + varDocs = append(varDocs, vd) + } + sort.Slice(varDocs, func(i, j int) bool { + return varDocs[i].Name < varDocs[j].Name + }) + mdFileLines := []string{ + "*This file is automatically generated via ./cmd/gendoc*", + "", + "## Complement-Crypto Configuration", + "Complement is configured exclusively through the use of environment variables. These variables are described below. Additional environment variables can be used, and are outlined at https://github.com/matrix-org/complement/blob/main/ENVIRONMENT.md ", + } + for _, vd := range varDocs { + mdFileLines = append(mdFileLines, fmt.Sprintf("\n#### `%v`", vd.Name)) + mdFileLines = append(mdFileLines, vd.Description) + mdFileLines = append(mdFileLines, fmt.Sprintf("- Type: `%v`", vd.Type)) + if vd.Default != "" { + mdFileLines = append(mdFileLines, fmt.Sprintf("- Default: %v", vd.Default)) + } + } + fmt.Println(strings.Join(mdFileLines, "\n")) +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..abbeee3 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,85 @@ +package config + +import ( + "os" + "strings" + + "github.com/matrix-org/complement-crypto/internal/api" +) + +// The config for running Complement Crypto. This is configured using environment variables. The comments +// in this struct are structured so they can be automatically parsed via gendoc. See /cmd/gendoc. +// There are additional configuration options available: see https://github.com/matrix-org/complement/blob/main/ENVIRONMENT.md +type ComplementCrypto struct { + // Name: COMPLEMENT_CRYPTO_WRITE_CONTAINER_LOGS + // Default: 0 + // Description: If 1, writes container logs to ./tests. Useful as a debugging tool. + WriteContainerLogs bool + + // Name: COMPLEMENT_CRYPTO_TEST_CLIENT_MATRIX + // Default: jj,jr,rj,rr + // Description: The client test matrix to run. Every test is run for each given permutation. + // The default matrix tests all JS/Rust permutations _ignoring federation_. + // Valid values are: + // - `j`: Run a JS SDK client on hs1. + // - `r`: Run a Rust SDK FFI client on hs1. + // - `J`: Run a JS SDK client on hs2. + // - `R`: Run a Rust SDK FFI client on hs2. TODO: needs additional SS proxy / postgres. + // For example, for a simple "Alice and Bob" test: + // - `rj,rr`: Run the test twice. Run 1: Alice=rust, Bob=JS. Run 2: Alice=rust, Bob=rust. All on HS1. + // - `jJ`: Run the test once. Run 1: Alice=JS on HS1, Bob=JS on HS2. Tests federation. + TestClientMatrix [][2]api.ClientType + + // Name: COMPLEMENT_CRYPTO_TCPDUMP + // Default: 0 + // Description: If 1, automatically attempts to run `tcpdump` when the containers are running. Stops dumping when + // tests complete. This will probably require you to run `go test` with `sudo -E`. The `.pcap` file is written to + // `tests/test.pcap`. + TCPDump bool +} + +func NewComplementCryptoConfigFromEnvVars() *ComplementCrypto { + matrix := os.Getenv("COMPLEMENT_CRYPTO_TEST_CLIENT_MATRIX") + if matrix == "" { + matrix = "jj,jr,rj,rr" + } + segs := strings.Split(matrix, ",") + var testClientMatrix [][2]api.ClientType + for _, val := range segs { // e.g val == 'rj' + if len(val) != 2 { + panic("COMPLEMENT_CRYPTO_TEST_CLIENT_MATRIX bad value: " + val) + } + testCase := [2]api.ClientType{} + for i, ch := range val { + switch ch { + case 'r': + testCase[i] = api.ClientType{ + Lang: api.ClientTypeRust, + HS: "hs1", + } + case 'j': + testCase[i] = api.ClientType{ + Lang: api.ClientTypeJS, + HS: "hs1", + } + case 'J': + testCase[i] = api.ClientType{ + Lang: api.ClientTypeJS, + HS: "hs2", + } + // TODO: case 'R': requires 2x sliding syncs / postgres + default: + panic("COMPLEMENT_CRYPTO_TEST_CLIENT_MATRIX bad value: " + val) + } + } + testClientMatrix = append(testClientMatrix, testCase) + } + if len(testClientMatrix) == 0 { + panic("COMPLEMENT_CRYPTO_TEST_CLIENT_MATRIX: no tests will run as no matrix values are set") + } + return &ComplementCrypto{ + WriteContainerLogs: os.Getenv("COMPLEMENT_CRYPTO_WRITE_CONTAINER_LOGS") == "1", + TCPDump: os.Getenv("COMPLEMENT_CRYPTO_TCPDUMP") == "1", + TestClientMatrix: testClientMatrix, + } +} diff --git a/tests/main_test.go b/tests/main_test.go index f2eb014..d19141a 100644 --- a/tests/main_test.go +++ b/tests/main_test.go @@ -2,13 +2,12 @@ package tests import ( "fmt" - "os" - "strings" "sync" "testing" "github.com/matrix-org/complement" "github.com/matrix-org/complement-crypto/internal/api" + "github.com/matrix-org/complement-crypto/internal/config" "github.com/matrix-org/complement-crypto/internal/deploy" "github.com/matrix-org/complement/client" "github.com/matrix-org/complement/helpers" @@ -16,52 +15,19 @@ import ( ) var ( - ssDeployment *deploy.SlidingSyncDeployment - ssMutex *sync.Mutex - testClientMatrix = [][2]api.ClientType{} // set in TestMain + ssDeployment *deploy.SlidingSyncDeployment + ssMutex *sync.Mutex + complementCryptoConfig *config.ComplementCrypto // set in TestMain ) func TestMain(m *testing.M) { - ccTestClients := os.Getenv("COMPLEMENT_CRYPTO_TEST_CLIENT_MATRIX") - if ccTestClients == "" { - ccTestClients = "jj,jr,rj,rr" - } - segs := strings.Split(ccTestClients, ",") - for _, val := range segs { // e.g val == 'rj' - if len(val) != 2 { - panic("COMPLEMENT_CRYPTO_TEST_CLIENT_MATRIX bad value: " + val) - } - testCase := [2]api.ClientType{} - for i, ch := range val { - switch ch { - case 'r': - testCase[i] = api.ClientType{ - Lang: api.ClientTypeRust, - HS: "hs1", - } - case 'j': - testCase[i] = api.ClientType{ - Lang: api.ClientTypeJS, - HS: "hs1", - } - case 'J': - testCase[i] = api.ClientType{ - Lang: api.ClientTypeJS, - HS: "hs2", - } - // TODO: case 'R': requires 2x sliding syncs / postgres - default: - panic("COMPLEMENT_CRYPTO_TEST_CLIENT_MATRIX bad value: " + val) - } - } - testClientMatrix = append(testClientMatrix, testCase) - } + complementCryptoConfig = config.NewComplementCryptoConfigFromEnvVars() ssMutex = &sync.Mutex{} api.SetupJSLogs("js_sdk.log") // rust sdk logs on its own complement.TestMainWithCleanup(m, "crypto", func() { // always teardown even if panicking ssMutex.Lock() if ssDeployment != nil { - ssDeployment.Teardown(os.Getenv("COMPLEMENT_CRYPTO_WRITE_CONTAINER_LOGS") == "1") + ssDeployment.Teardown(complementCryptoConfig.WriteContainerLogs) } ssMutex.Unlock() api.WriteJSLogs() @@ -74,12 +40,12 @@ func Deploy(t *testing.T) *deploy.SlidingSyncDeployment { if ssDeployment != nil { return ssDeployment } - ssDeployment = deploy.RunNewDeployment(t, os.Getenv("COMPLEMENT_CRYPTO_TCPDUMP") == "1") + ssDeployment = deploy.RunNewDeployment(t, complementCryptoConfig.TCPDump) return ssDeployment } func ClientTypeMatrix(t *testing.T, subTest func(tt *testing.T, a, b api.ClientType)) { - for _, tc := range testClientMatrix { + for _, tc := range complementCryptoConfig.TestClientMatrix { tc := tc t.Run(fmt.Sprintf("%s|%s", tc[0], tc[1]), func(t *testing.T) { subTest(t, tc[0], tc[1])