From e1587cc56c5c7b1ff820afb86bf094b79c26ecba Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 17 Feb 2026 20:18:09 -0600 Subject: [PATCH 01/32] Add in-repo Complement test to sanity check Synapse version matches git checkout --- .github/workflows/tests.yml | 44 ++++++------ .../tests/synapse_version_check_test.go | 70 +++++++++++++++++++ 2 files changed, 92 insertions(+), 22 deletions(-) create mode 100644 complement/tests/synapse_version_check_test.go diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 81a72f1f688..d61683df8b3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -707,28 +707,28 @@ jobs: cache-dependency-path: complement/go.sum go-version-file: complement/go.mod - - name: Run Complement Tests - id: run_complement_tests - # -p=1: We're using `-p 1` to force the test packages to run serially as GHA boxes - # are underpowered and don't like running tons of Synapse instances at once. - # -json: Output JSON format so that gotestfmt can parse it. - # - # tee /tmp/gotest-complement.log: We tee the output to a file so that we can re-process it - # later on for better formatting with gotestfmt. But we still want the command - # to output to the terminal as it runs so we can see what's happening in - # real-time. - run: | - set -o pipefail - COMPLEMENT_DIR=`pwd`/complement synapse/scripts-dev/complement.sh -p 1 -json 2>&1 | tee /tmp/gotest-complement.log - shell: bash - env: - POSTGRES: ${{ (matrix.database == 'Postgres') && 1 || '' }} - WORKERS: ${{ (matrix.arrangement == 'workers') && 1 || '' }} - - - name: Formatted Complement test logs - # Always run this step if we attempted to run the Complement tests. - if: always() && steps.run_complement_tests.outcome != 'skipped' - run: cat /tmp/gotest-complement.log | gotestfmt -hide "successful-downloads,empty-packages" + # - name: Run Complement Tests + # id: run_complement_tests + # # -p=1: We're using `-p 1` to force the test packages to run serially as GHA boxes + # # are underpowered and don't like running tons of Synapse instances at once. + # # -json: Output JSON format so that gotestfmt can parse it. + # # + # # tee /tmp/gotest-complement.log: We tee the output to a file so that we can re-process it + # # later on for better formatting with gotestfmt. But we still want the command + # # to output to the terminal as it runs so we can see what's happening in + # # real-time. + # run: | + # set -o pipefail + # COMPLEMENT_DIR=`pwd`/complement synapse/scripts-dev/complement.sh -p 1 -json 2>&1 | tee /tmp/gotest-complement.log + # shell: bash + # env: + # POSTGRES: ${{ (matrix.database == 'Postgres') && 1 || '' }} + # WORKERS: ${{ (matrix.arrangement == 'workers') && 1 || '' }} + + # - name: Formatted Complement test logs + # # Always run this step if we attempted to run the Complement tests. + # if: always() && steps.run_complement_tests.outcome != 'skipped' + # run: cat /tmp/gotest-complement.log | gotestfmt -hide "successful-downloads,empty-packages" - name: Run in-repo Complement Tests id: run_in_repo_complement_tests diff --git a/complement/tests/synapse_version_check_test.go b/complement/tests/synapse_version_check_test.go new file mode 100644 index 00000000000..13e960498dc --- /dev/null +++ b/complement/tests/synapse_version_check_test.go @@ -0,0 +1,70 @@ +// This file is licensed under the Affero General Public License (AGPL) version 3. +// +// Copyright (C) 2026 Element Creations Ltd +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// See the GNU Affero General Public License for more details: +// . + +package synapse_tests + +import ( + "testing" + + "github.com/matrix-org/complement" +) + +func TestSynapseVersion(t *testing.T) { + deployment := complement.Deploy(t, 1) + defer deployment.Destroy(t) + + unauthedClient := deployment.UnauthenticatedClient(); + + // Sanity check that the version of Synapse used in the `COMPLEMENT_BASE_IMAGE` + // matches the same commit we have checked out. This ensures that the image being used + // in Complement is the one that we just built locally with `complement.sh` instead of + // accidentally pulling some remote one. + t.Run("Synapse version matches current git checkout", func(t *testing.T) { + res := unauthedClient.MustDo(t, "GET", []string{"_matrix", "federation", "v1", "version"}) + // Cheeky way to get the response body + responseBody := must.MatchResponse(t, res, match.HTTPResponse{}) + synapseVersion := parseSynapseVersionString(string(responseBody)) + + // TODO: Get the details of the current checkout + + // TODO: Compare + }) +} + +type SynapseVersion struct { + // TODO + Version string + // TODO + Branch string + // TODO + Tag string + // TODO + Commit string + // TODO + Dirty bool +} + +// parseSynapseVersionString parses Synapse version strings in the format: +// +// - "1.147.1" +// - "1.147.1 (b=develop,b80774efb2)" +// - "1.147.1 (b=develop,b80774efb2,dirty)" +// - "1.147.1 (b=HEAD,t=v1.147.1,7ff8687653)" +// +// See +// https://github.com/matrix-org/matrix-python-common/blob/4084b21af839c50f775447d02ca4f1854e2e6191/src/matrix_common/versionstring.py +// for how Synapse generates these version strings. +func parseSynapseVersionString( + synapseVersionString string +) (*SynapseVersion, error) { + // TODO +} From 59df0d6e636242ddd286a47f4376512ecf5dda49 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 17 Feb 2026 20:39:46 -0600 Subject: [PATCH 02/32] Better comments --- .../tests/synapse_version_check_test.go | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/complement/tests/synapse_version_check_test.go b/complement/tests/synapse_version_check_test.go index 13e960498dc..a012d2d59f1 100644 --- a/complement/tests/synapse_version_check_test.go +++ b/complement/tests/synapse_version_check_test.go @@ -16,6 +16,8 @@ import ( "testing" "github.com/matrix-org/complement" + "github.com/matrix-org/complement/match" + "github.com/matrix-org/complement/must" ) func TestSynapseVersion(t *testing.T) { @@ -25,16 +27,23 @@ func TestSynapseVersion(t *testing.T) { unauthedClient := deployment.UnauthenticatedClient(); // Sanity check that the version of Synapse used in the `COMPLEMENT_BASE_IMAGE` - // matches the same commit we have checked out. This ensures that the image being used - // in Complement is the one that we just built locally with `complement.sh` instead of - // accidentally pulling some remote one. + // matches the same git commit we have checked out. This ensures that the image being + // used in Complement is the one that we just built locally with `complement.sh` + // instead of accidentally pulling in some remote one. + // + // This test is expected to pass if you use `complement.sh`. + // + // If this test fails, it probably means that Complement is using an image that + // doesn't encompass the changes you have checked out (unexpected). We want to yell + // loudly and point out what's wrong instead of silently letting your PR's pass + // without actually being tested. t.Run("Synapse version matches current git checkout", func(t *testing.T) { res := unauthedClient.MustDo(t, "GET", []string{"_matrix", "federation", "v1", "version"}) // Cheeky way to get the response body responseBody := must.MatchResponse(t, res, match.HTTPResponse{}) synapseVersion := parseSynapseVersionString(string(responseBody)) - // TODO: Get the details of the current checkout + // TODO: Get the details of the current git checkout // TODO: Compare }) @@ -53,7 +62,8 @@ type SynapseVersion struct { Dirty bool } -// parseSynapseVersionString parses Synapse version strings in the format: +// parseSynapseVersionString parses Synapse version strings (from +// `GET /_matrix/federation/v1/version`) in the format: // // - "1.147.1" // - "1.147.1 (b=develop,b80774efb2)" @@ -64,7 +74,7 @@ type SynapseVersion struct { // https://github.com/matrix-org/matrix-python-common/blob/4084b21af839c50f775447d02ca4f1854e2e6191/src/matrix_common/versionstring.py // for how Synapse generates these version strings. func parseSynapseVersionString( - synapseVersionString string + synapseVersionString string, ) (*SynapseVersion, error) { // TODO } From 49d2d2704cdb0e707bd85e1aa50a467a214806b3 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 18 Feb 2026 11:37:56 -0600 Subject: [PATCH 03/32] Add tests for `parseSynapseVersionString` --- complement/go.mod | 1 + complement/go.sum | 12 ++ .../tests/synapse_version_check_test.go | 106 +++++++++++++++++- 3 files changed, 114 insertions(+), 5 deletions(-) diff --git a/complement/go.mod b/complement/go.mod index c6c1678bacc..2a9adb9b955 100644 --- a/complement/go.mod +++ b/complement/go.mod @@ -31,6 +31,7 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 // indirect github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.2 // indirect diff --git a/complement/go.sum b/complement/go.sum index c674730c057..79a35aa14c6 100644 --- a/complement/go.sum +++ b/complement/go.sum @@ -38,6 +38,8 @@ 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/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +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= github.com/hashicorp/go-set/v3 v3.0.0/go.mod h1:IEghM2MpE5IaNvL+D7X480dfNtxjRXZ6VMpK3C8s2ok= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -50,6 +52,8 @@ github.com/matrix-org/gomatrixserverlib v0.0.0-20250813150445-9f5070a65744 h1:5G github.com/matrix-org/gomatrixserverlib v0.0.0-20250813150445-9f5070a65744/go.mod h1:b6KVfDjXjA5Q7vhpOaMqIhFYvu5BuFVZixlNeTV/CLc= github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 h1:6z4KxomXSIGWqhHcfzExgkH3Z3UkIXry4ibJS4Aqz2Y= github.com/matrix-org/util v0.0.0-20221111132719-399730281e66/go.mod h1:iBI1foelCqA09JJgPV0FYz4qA5dUXYOxMi57FxKBdd4= +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= @@ -120,6 +124,8 @@ golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -129,6 +135,8 @@ golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -147,6 +155,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= 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= @@ -160,6 +170,8 @@ google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3i google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/complement/tests/synapse_version_check_test.go b/complement/tests/synapse_version_check_test.go index a012d2d59f1..e27482c77c0 100644 --- a/complement/tests/synapse_version_check_test.go +++ b/complement/tests/synapse_version_check_test.go @@ -13,6 +13,8 @@ package synapse_tests import ( + "reflect" + "strings" "testing" "github.com/matrix-org/complement" @@ -24,7 +26,7 @@ func TestSynapseVersion(t *testing.T) { deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) - unauthedClient := deployment.UnauthenticatedClient(); + unauthedClient := deployment.UnauthenticatedClient(t, "hs1"); // Sanity check that the version of Synapse used in the `COMPLEMENT_BASE_IMAGE` // matches the same git commit we have checked out. This ensures that the image being @@ -38,14 +40,21 @@ func TestSynapseVersion(t *testing.T) { // loudly and point out what's wrong instead of silently letting your PR's pass // without actually being tested. t.Run("Synapse version matches current git checkout", func(t *testing.T) { + // TODO: Pull the `version` from `pyproject.toml`. + + // TODO: Get the details of the current git checkout + + // Find the version details of the Synapse instance deployed from the Docker image res := unauthedClient.MustDo(t, "GET", []string{"_matrix", "federation", "v1", "version"}) // Cheeky way to get the response body responseBody := must.MatchResponse(t, res, match.HTTPResponse{}) - synapseVersion := parseSynapseVersionString(string(responseBody)) - - // TODO: Get the details of the current git checkout + synapseVersion, err := parseSynapseVersionString(string(responseBody)) + if err != nil { + t.Fatalf("Failed to parse Synapse version string: %v", err) + } // TODO: Compare + _ = synapseVersion }) } @@ -76,5 +85,92 @@ type SynapseVersion struct { func parseSynapseVersionString( synapseVersionString string, ) (*SynapseVersion, error) { - // TODO + // We're trying to separate "1.147.1" and "(...)" + parts := strings.SplitN(synapseVersionString, " ", 2) + + version := parts[0] + gitString := "" + if len(parts) == 2 { + gitString = parts[1] + // Remove the surrounding parenthesis (...) + gitString = strings.TrimPrefix(gitString, "(") + gitString = strings.TrimSuffix(gitString, "(") + } + + branch := "" + tag := "" + commit := "" + dirty := false + gitParts := strings.Split(gitString, ",", 4) + + return &SynapseVersion{ + Version: version, + Branch: branch, + Tag: tag, + Commit: commit, + Dirty: dirty, + }, nil +} + + +func TestParseSynapseVersionString(t *testing.T) { + testCases := []struct { + name string + input string + expected *SynapseVersion + wantErr bool + }{ + { + name: "simple version", + input: "1.147.1", + expected: &SynapseVersion{ + Version: "1.147.1", + Dirty: false, + }, + }, + { + name: "version with branch and commit", + input: "1.147.1 (b=develop,b80774efb2)", + expected: &SynapseVersion{ + Version: "1.147.1", + Branch: "develop", + Commit: "b80774efb2", + Dirty: false, + }, + }, + { + name: "version with branch, commit, and dirty", + input: "1.147.1 (b=develop,b80774efb2,dirty)", + expected: &SynapseVersion{ + Version: "1.147.1", + Branch: "develop", + Commit: "b80774efb2", + Dirty: true, + }, + }, + { + name: "version with HEAD branch, tag, and commit", + input: "1.147.1 (b=HEAD,t=v1.147.1,7ff8687653)", + expected: &SynapseVersion{ + Version: "1.147.1", + Branch: "HEAD", + Tag: "v1.147.1", + Commit: "7ff8687653", + Dirty: false, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + got, err := parseSynapseVersionString(testCase.input) + if err != nil { + t.Errorf("parseSynapseVersionString(%s) failed to parse input, error: %v", testCase.input, err) + } + + if !reflect.DeepEqual(got, testCase.expected) { + t.Errorf("parseSynapseVersionString(%s) got %v, want %v", testCase.input, got, testCase.expected) + } + }) + } } From ce1ae5764250b7f74f5f9f2f8db4c5e676a050ed Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 18 Feb 2026 12:41:19 -0600 Subject: [PATCH 04/32] Working `parseSynapseVersionString` --- .../tests/synapse_version_check_test.go | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/complement/tests/synapse_version_check_test.go b/complement/tests/synapse_version_check_test.go index e27482c77c0..2052457cbed 100644 --- a/complement/tests/synapse_version_check_test.go +++ b/complement/tests/synapse_version_check_test.go @@ -13,6 +13,7 @@ package synapse_tests import ( + "fmt" "reflect" "strings" "testing" @@ -94,14 +95,37 @@ func parseSynapseVersionString( gitString = parts[1] // Remove the surrounding parenthesis (...) gitString = strings.TrimPrefix(gitString, "(") - gitString = strings.TrimSuffix(gitString, "(") + gitString = strings.TrimSuffix(gitString, ")") } branch := "" tag := "" commit := "" dirty := false - gitParts := strings.Split(gitString, ",", 4) + gitParts := strings.Split(gitString, ",") + + // Go through piece by piece and try to match it up as best as possible. The pieces + // should always be in order (branch, tag, commit, dirty) according to the source code + // that generates them. + for _, gitPart := range gitParts { + // Match the most specific key=value pairs first + if branch == "" && strings.HasPrefix(gitPart, "b=") { + branch = strings.TrimPrefix(gitPart, "b=") + } else if tag == "" && strings.HasPrefix(gitPart, "t=") { + tag = strings.TrimPrefix(gitPart, "t=") + } else { + // Otherwise, we have to match remaining the bare pieces. + // + // The first bare piece should be the commit + if commit == "" { + commit = gitPart + } else if gitPart == "dirty" { + dirty = true + } else { + return nil, fmt.Errorf("parseSynapseVersionString: Unexpected format of git details (%s)", synapseVersionString) + } + } + } return &SynapseVersion{ Version: version, @@ -165,11 +189,11 @@ func TestParseSynapseVersionString(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { got, err := parseSynapseVersionString(testCase.input) if err != nil { - t.Errorf("parseSynapseVersionString(%s) failed to parse input, error: %v", testCase.input, err) + t.Errorf("parseSynapseVersionString(\"%s\") failed to parse input, error: %v", testCase.input, err) } if !reflect.DeepEqual(got, testCase.expected) { - t.Errorf("parseSynapseVersionString(%s) got %v, want %v", testCase.input, got, testCase.expected) + t.Errorf("parseSynapseVersionString(\"%s\") got %v, want %v", testCase.input, got, testCase.expected) } }) } From 5189af94ec2253ef54a8b279c47455a9f399ca0b Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 18 Feb 2026 13:39:07 -0600 Subject: [PATCH 05/32] Run git commands --- .../tests/synapse_version_check_test.go | 81 ++++++++++++++++--- 1 file changed, 69 insertions(+), 12 deletions(-) diff --git a/complement/tests/synapse_version_check_test.go b/complement/tests/synapse_version_check_test.go index 2052457cbed..1dd1d6b6875 100644 --- a/complement/tests/synapse_version_check_test.go +++ b/complement/tests/synapse_version_check_test.go @@ -14,6 +14,8 @@ package synapse_tests import ( "fmt" + "net/http" + "os/exec" "reflect" "strings" "testing" @@ -21,6 +23,7 @@ import ( "github.com/matrix-org/complement" "github.com/matrix-org/complement/match" "github.com/matrix-org/complement/must" + "github.com/tidwall/gjson" ) func TestSynapseVersion(t *testing.T) { @@ -42,33 +45,87 @@ func TestSynapseVersion(t *testing.T) { // without actually being tested. t.Run("Synapse version matches current git checkout", func(t *testing.T) { // TODO: Pull the `version` from `pyproject.toml`. + checkoutVersion := "TODO" - // TODO: Get the details of the current git checkout + // Get the details of the current git checkout + // + // Commands match https://github.com/matrix-org/matrix-python-common/blob/4084b21af839c50f775447d02ca4f1854e2e6191/src/matrix_common/versionstring.py#L87C45-L95 + gitBranch := runGitCommand(t, []string{"git", "rev-parse", "--abbrev-ref", "HEAD"}) + gitTag := runGitCommand(t, []string{"git", "describe", "--exact-match"}) + gitCommit := runGitCommand(t, []string{"git", "rev-parse", "--short", "HEAD"}) + gitDirty := strings.HasSuffix( + runGitCommand(t, []string{"git", "describe", "--dirty='-this_is_a_dirty_checkout'"}), + "-this_is_a_dirty_checkout", + ) + + // Assemble our checkout details into a `SynapseVersion` we can easily compare with + checkoutSynapseVersion := SynapseVersion { + Version: checkoutVersion, + Branch: gitBranch, + Tag: gitTag, + Commit: gitCommit, + Dirty: gitDirty, + } // Find the version details of the Synapse instance deployed from the Docker image res := unauthedClient.MustDo(t, "GET", []string{"_matrix", "federation", "v1", "version"}) - // Cheeky way to get the response body - responseBody := must.MatchResponse(t, res, match.HTTPResponse{}) - synapseVersion, err := parseSynapseVersionString(string(responseBody)) + body := must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: http.StatusOK, + JSON: []match.JSON{ + match.JSONKeyPresent("server"), + }, + }) + rawSynapseVersionString := gjson.GetBytes(body, "server.version").Str + t.Logf("Synapse version string from federation version endpoint: %s", rawSynapseVersionString) + synapseVersion, err := parseSynapseVersionString(rawSynapseVersionString) if err != nil { t.Fatalf("Failed to parse Synapse version string: %v", err) } - // TODO: Compare - _ = synapseVersion + // Compare + if !reflect.DeepEqual(synapseVersion, checkoutSynapseVersion) { + t.Errorf( + "Synapse version in the checkout doesn't match the Synapse version that Complement is running. " + + "From the Checkout: %v, From the Complement image: %v", checkoutSynapseVersion, synapseVersion, + ) + } }) } +// runGitCommand will run the given git command and return the stdout (whitespace +// trimmed). +// +// If it's not a git repo, will just return a blank string (for ease-of-use in the +// tests) +func runGitCommand(t *testing.T, commandPieces []string) string { + t.Helper() + + // Check if this is even a git repo + cmd := exec.Command("git", "status") + if cmd.ProcessState.ExitCode() != 0 { + return "" + } + + // Then run our actual command + cmd = exec.Command(commandPieces[0], commandPieces[1:]...) + output, err := cmd.Output() + if err != nil { + t.Fatalf("runGitCommand: failed to run command (%s): %v", strings.Join(commandPieces, " "), err) + } + + return strings.TrimSpace(string(output)) +} + type SynapseVersion struct { - // TODO + // Synapse version Version string - // TODO + // git branch Branch string - // TODO + // git tag Tag string - // TODO + // git commit Commit string - // TODO + // Whether the git repo has uncommitted changes in it Dirty bool } @@ -136,7 +193,7 @@ func parseSynapseVersionString( }, nil } - +// Sanity check that our `parseSynapseVersionString` utility works as expected func TestParseSynapseVersionString(t *testing.T) { testCases := []struct { name string From 77ed60fd54c7c569582a17941792957ded1213fd Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 18 Feb 2026 14:17:03 -0600 Subject: [PATCH 06/32] `getVersionFromPyproject` --- .../tests/synapse_version_check_test.go | 52 ++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/complement/tests/synapse_version_check_test.go b/complement/tests/synapse_version_check_test.go index 1dd1d6b6875..01906b736d2 100644 --- a/complement/tests/synapse_version_check_test.go +++ b/complement/tests/synapse_version_check_test.go @@ -15,8 +15,10 @@ package synapse_tests import ( "fmt" "net/http" + "os" "os/exec" "reflect" + "regexp" "strings" "testing" @@ -44,8 +46,8 @@ func TestSynapseVersion(t *testing.T) { // loudly and point out what's wrong instead of silently letting your PR's pass // without actually being tested. t.Run("Synapse version matches current git checkout", func(t *testing.T) { - // TODO: Pull the `version` from `pyproject.toml`. - checkoutVersion := "TODO" + // Pull the `version` from `pyproject.toml`. + pyprojectVersion := getVersionFromPyproject(t) // Get the details of the current git checkout // @@ -60,7 +62,7 @@ func TestSynapseVersion(t *testing.T) { // Assemble our checkout details into a `SynapseVersion` we can easily compare with checkoutSynapseVersion := SynapseVersion { - Version: checkoutVersion, + Version: pyprojectVersion, Branch: gitBranch, Tag: gitTag, Commit: gitCommit, @@ -92,25 +94,49 @@ func TestSynapseVersion(t *testing.T) { }) } + +// Helper function to get `version` from pyproject.toml +func getVersionFromPyproject(t *testing.T) string { + // Read pyproject.toml file + // + // Log the current working directory so it's easier to understand what's going on here + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("getVersionFromPyproject: failed to get current working directory: %v", err) + } + t.Logf("getVersionFromPyproject: currenty working directory is %s", cwd) + // This path is relative to this file + fileContents, err := os.ReadFile("../../pyproject.toml") + if err != nil { + t.Fatalf("getVersionFromPyproject: failed to read `pyproject.toml`: %v", err) + } + + // Instead of parsing toml, we just do a dirty find of the first `version = "1.147.1"` + re := regexp.MustCompile( + `(?m)^version = "(?P.*?)"\s?$`, + ) + match := re.FindStringSubmatch(string(fileContents)) + if len(match) == 0 { + t.Fatalf("getVersionFromPyproject: Unable to find `version` in `pyproject.toml`") + } + + return match[re.SubexpIndex("Version")] +} + // runGitCommand will run the given git command and return the stdout (whitespace // trimmed). // -// If it's not a git repo, will just return a blank string (for ease-of-use in the -// tests) +// Errors will be logged but this function will just return a blank string (for +// ease-of-use in the tests) func runGitCommand(t *testing.T, commandPieces []string) string { t.Helper() - // Check if this is even a git repo - cmd := exec.Command("git", "status") - if cmd.ProcessState.ExitCode() != 0 { - return "" - } - // Then run our actual command - cmd = exec.Command(commandPieces[0], commandPieces[1:]...) + cmd := exec.Command(commandPieces[0], commandPieces[1:]...) output, err := cmd.Output() if err != nil { - t.Fatalf("runGitCommand: failed to run command (%s): %v", strings.Join(commandPieces, " "), err) + t.Logf("runGitCommand: failed to run command (%s) (this may be expected depending on the command): %v", strings.Join(commandPieces, " "), err) + return "" } return strings.TrimSpace(string(output)) From 991ab597a114a2764c0c068a1892386a2327d439 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 18 Feb 2026 14:19:40 -0600 Subject: [PATCH 07/32] Better error message --- complement/tests/synapse_version_check_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/complement/tests/synapse_version_check_test.go b/complement/tests/synapse_version_check_test.go index 01906b736d2..e11b9bc4ac6 100644 --- a/complement/tests/synapse_version_check_test.go +++ b/complement/tests/synapse_version_check_test.go @@ -86,9 +86,13 @@ func TestSynapseVersion(t *testing.T) { // Compare if !reflect.DeepEqual(synapseVersion, checkoutSynapseVersion) { - t.Errorf( + t.Fatalf( "Synapse version in the checkout doesn't match the Synapse version that Complement is running. " + - "From the Checkout: %v, From the Complement image: %v", checkoutSynapseVersion, synapseVersion, + "From the Checkout: %v, From the Complement image: %v\n\n" + + "If this test fails, it probably means that Complement is using an image that " + + "doesn't encompass the changes you have checked out (unexpected). We want to yell " + + "loudly and point out what's wrong instead of silently letting your PR's pass " + + "without actually being tested.", checkoutSynapseVersion, synapseVersion, ) } }) From 639402bc1c18ab95ddc0711fe5b867bdc2c8bb2a Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 18 Feb 2026 14:22:05 -0600 Subject: [PATCH 08/32] Fix dirty checkout command --- complement/tests/synapse_version_check_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/complement/tests/synapse_version_check_test.go b/complement/tests/synapse_version_check_test.go index e11b9bc4ac6..aa36189bb6c 100644 --- a/complement/tests/synapse_version_check_test.go +++ b/complement/tests/synapse_version_check_test.go @@ -56,7 +56,7 @@ func TestSynapseVersion(t *testing.T) { gitTag := runGitCommand(t, []string{"git", "describe", "--exact-match"}) gitCommit := runGitCommand(t, []string{"git", "rev-parse", "--short", "HEAD"}) gitDirty := strings.HasSuffix( - runGitCommand(t, []string{"git", "describe", "--dirty='-this_is_a_dirty_checkout'"}), + runGitCommand(t, []string{"git", "describe", "--dirty=-this_is_a_dirty_checkout"}), "-this_is_a_dirty_checkout", ) From 29029e56868d3e06f9e0616711aaf19a96d2f2f5 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 18 Feb 2026 14:23:48 -0600 Subject: [PATCH 09/32] Add changelog --- changelog.d/19476.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/19476.misc diff --git a/changelog.d/19476.misc b/changelog.d/19476.misc new file mode 100644 index 00000000000..c1869911a47 --- /dev/null +++ b/changelog.d/19476.misc @@ -0,0 +1 @@ +Add in-repo Complement test to sanity check Synapse version matches git checkout (testing what we think we are). From 52936223befffc378f5636cf7e7a09ee256d0a78 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 18 Feb 2026 16:47:48 -0600 Subject: [PATCH 10/32] Try adding `.git` data --- .dockerignore | 4 ++++ docker/complement/Dockerfile | 9 ++++++--- scripts-dev/complement.sh | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.dockerignore b/.dockerignore index 0b51345cbdb..d8c879690b9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,6 +2,10 @@ * # things to include +!.git +# !.git/refs/heads +# !.git/config +# !.git/HEAD !docker !synapse !rust diff --git a/docker/complement/Dockerfile b/docker/complement/Dockerfile index ec4bca232c3..81852b56e0d 100644 --- a/docker/complement/Dockerfile +++ b/docker/complement/Dockerfile @@ -42,14 +42,17 @@ RUN echo "CREATE DATABASE synapse" | gosu postgres postgres --single # To do this, we copy the old template out of the way and then include it # with Jinja2. RUN mv /conf/shared.yaml.j2 /conf/shared-orig.yaml.j2 -COPY conf/workers-shared-extra.yaml.j2 /conf/shared.yaml.j2 +COPY docker/complement/conf/workers-shared-extra.yaml.j2 /conf/shared.yaml.j2 WORKDIR /data -COPY conf/postgres.supervisord.conf /etc/supervisor/conf.d/postgres.conf +# TODO: Copies but not to the right spot for for `SYNAPSE_VERSION` to pick it up +COPY .git /data/.git/ + +COPY docker/complement/conf/postgres.supervisord.conf /etc/supervisor/conf.d/postgres.conf # Copy the entrypoint -COPY conf/start_for_complement.sh / +COPY docker/complement/conf/start_for_complement.sh / # Expose nginx's listener ports EXPOSE 8008 8448 diff --git a/scripts-dev/complement.sh b/scripts-dev/complement.sh index dc2262a2a35..79a852a5953 100755 --- a/scripts-dev/complement.sh +++ b/scripts-dev/complement.sh @@ -213,7 +213,7 @@ main() { `# .github/workflows/push_complement_image.yml) so let's just label it now` \ `# so people can reference it by the same name locally.` \ -t ghcr.io/element-hq/synapse/complement-synapse \ - -f "docker/complement/Dockerfile" "docker/complement" + -f "docker/complement/Dockerfile" . echo_if_github "::endgroup::" fi From bc2c676f478ad19101d00275dac9d81a89199ca6 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 18 Feb 2026 16:48:04 -0600 Subject: [PATCH 11/32] Revert "Try adding `.git` data" This reverts commit 52936223befffc378f5636cf7e7a09ee256d0a78. --- .dockerignore | 4 ---- docker/complement/Dockerfile | 9 +++------ scripts-dev/complement.sh | 2 +- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/.dockerignore b/.dockerignore index d8c879690b9..0b51345cbdb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,10 +2,6 @@ * # things to include -!.git -# !.git/refs/heads -# !.git/config -# !.git/HEAD !docker !synapse !rust diff --git a/docker/complement/Dockerfile b/docker/complement/Dockerfile index 81852b56e0d..ec4bca232c3 100644 --- a/docker/complement/Dockerfile +++ b/docker/complement/Dockerfile @@ -42,17 +42,14 @@ RUN echo "CREATE DATABASE synapse" | gosu postgres postgres --single # To do this, we copy the old template out of the way and then include it # with Jinja2. RUN mv /conf/shared.yaml.j2 /conf/shared-orig.yaml.j2 -COPY docker/complement/conf/workers-shared-extra.yaml.j2 /conf/shared.yaml.j2 +COPY conf/workers-shared-extra.yaml.j2 /conf/shared.yaml.j2 WORKDIR /data -# TODO: Copies but not to the right spot for for `SYNAPSE_VERSION` to pick it up -COPY .git /data/.git/ - -COPY docker/complement/conf/postgres.supervisord.conf /etc/supervisor/conf.d/postgres.conf +COPY conf/postgres.supervisord.conf /etc/supervisor/conf.d/postgres.conf # Copy the entrypoint -COPY docker/complement/conf/start_for_complement.sh / +COPY conf/start_for_complement.sh / # Expose nginx's listener ports EXPOSE 8008 8448 diff --git a/scripts-dev/complement.sh b/scripts-dev/complement.sh index 79a852a5953..dc2262a2a35 100755 --- a/scripts-dev/complement.sh +++ b/scripts-dev/complement.sh @@ -213,7 +213,7 @@ main() { `# .github/workflows/push_complement_image.yml) so let's just label it now` \ `# so people can reference it by the same name locally.` \ -t ghcr.io/element-hq/synapse/complement-synapse \ - -f "docker/complement/Dockerfile" . + -f "docker/complement/Dockerfile" "docker/complement" echo_if_github "::endgroup::" fi From aef8a8805f79d03ba65976a06801985e3158041b Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 18 Feb 2026 17:28:56 -0600 Subject: [PATCH 12/32] Pretty print structs we compare --- complement/tests/synapse_version_check_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/complement/tests/synapse_version_check_test.go b/complement/tests/synapse_version_check_test.go index aa36189bb6c..94668cfe2e0 100644 --- a/complement/tests/synapse_version_check_test.go +++ b/complement/tests/synapse_version_check_test.go @@ -88,7 +88,7 @@ func TestSynapseVersion(t *testing.T) { if !reflect.DeepEqual(synapseVersion, checkoutSynapseVersion) { t.Fatalf( "Synapse version in the checkout doesn't match the Synapse version that Complement is running. " + - "From the Checkout: %v, From the Complement image: %v\n\n" + + "From the Checkout: %+v, From the Complement image: %+v\n\n" + "If this test fails, it probably means that Complement is using an image that " + "doesn't encompass the changes you have checked out (unexpected). We want to yell " + "loudly and point out what's wrong instead of silently letting your PR's pass " + From b58aab60c954347cb08a8c37c95833683cd399be Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 18 Feb 2026 18:30:06 -0600 Subject: [PATCH 13/32] Start of `SYNAPSE_VERSION` --- docker/Dockerfile | 10 ++++++++++ pyproject.toml | 4 ++++ scripts-dev/complement.sh | 2 ++ synapse/util/__init__.py | 2 ++ 4 files changed, 18 insertions(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index 9f74e48df35..e67020e29e0 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -67,6 +67,12 @@ RUN --mount=type=cache,target=/root/.cache/uv \ ### FROM ghcr.io/astral-sh/uv:python${PYTHON_VERSION}-${DEBIAN_VERSION} AS builder +# If specified, Synapse will use this as the version string in the app. +# +# This can be useful to capture the git info of the build as `.git/` won't be be +# available in the Docker image for Synapse to generate from. +ARG SYNAPSE_VERSION_STRING + # This silences a warning as uv isn't able to do hardlinks between its cache # (mounted as --mount=type=cache) and the target directory. ENV UV_LINK_MODE=copy @@ -98,6 +104,10 @@ COPY synapse /synapse/synapse/ COPY rust /synapse/rust/ # ... and what we need to `pip install`. COPY pyproject.toml README.rst build_rust.py Cargo.toml Cargo.lock /synapse/ +# Create a `SYNAPSE_VERSION` file if `SYNAPSE_VERSION_STRING` was specified +RUN if [ -n "${SYNAPSE_VERSION_STRING}" ]; then \ + echo ${SYNAPSE_VERSION_STRING} > /synapse/SYNAPSE_VERSION; \ + fi # Repeat of earlier build argument declaration, as this is a new build stage. ARG TEST_ONLY_IGNORE_POETRY_LOCKFILE diff --git a/pyproject.toml b/pyproject.toml index 8073f8ec44f..2b0b5e7ee1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -416,6 +416,10 @@ manifest-path = "rust/Cargo.toml" module-name = "synapse.synapse_rust" python-source = "." include = [ + { path = "SYNAPSE_VERSION", format = [ + "sdist", + "wheel", + ] }, { path = "AUTHORS.rst", format = "sdist" }, { path = "book.toml", format = "sdist" }, { path = "changelog.d/**/*", format = "sdist" }, diff --git a/scripts-dev/complement.sh b/scripts-dev/complement.sh index dc2262a2a35..6cd88290113 100755 --- a/scripts-dev/complement.sh +++ b/scripts-dev/complement.sh @@ -192,10 +192,12 @@ main() { $CONTAINER_RUNTIME run --rm -v $editable_mount --entrypoint 'cp' complement-synapse-editable -- /synapse_rust.abi3.so.bak /editable-src/synapse/synapse_rust.abi3.so else + synapse_version_string="$(poetry run python -c 'from synapse.util import SYNAPSE_VERSION; print(SYNAPSE_VERSION)')" # Build the base Synapse image from the local checkout echo_if_github "::group::Build Docker image: matrixdotorg/synapse" $CONTAINER_RUNTIME build -t matrixdotorg/synapse \ + --build-arg SYNAPSE_VERSION_STRING="$synapse_version_string" \ --build-arg TEST_ONLY_SKIP_DEP_HASH_VERIFICATION \ --build-arg TEST_ONLY_IGNORE_POETRY_LOCKFILE \ -f "docker/Dockerfile" . diff --git a/synapse/util/__init__.py b/synapse/util/__init__.py index fbd01914d56..23319743cc8 100644 --- a/synapse/util/__init__.py +++ b/synapse/util/__init__.py @@ -76,6 +76,8 @@ def log_failure( # Version string with git info. Computed here once so that we don't invoke git multiple # times. +# +# TODO: Read from SYNAPSE_VERSION file if it exists SYNAPSE_VERSION = get_distribution_version_string("matrix-synapse", __file__) From 6ce2ca29af63b7f7d2da66ab837f070cf1c549e3 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 20 Feb 2026 09:55:34 -0600 Subject: [PATCH 14/32] Fix `SYNAPSE_VERSION` not appearing under `/usr/local/lib/python3.13/site-packages/synapse/SYNAPSE_VERSION` See https://github.com/element-hq/synapse/pull/19476#discussion_r2832613526 --- docker/Dockerfile | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index e67020e29e0..e3ffc3db0a0 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -106,7 +106,7 @@ COPY rust /synapse/rust/ COPY pyproject.toml README.rst build_rust.py Cargo.toml Cargo.lock /synapse/ # Create a `SYNAPSE_VERSION` file if `SYNAPSE_VERSION_STRING` was specified RUN if [ -n "${SYNAPSE_VERSION_STRING}" ]; then \ - echo ${SYNAPSE_VERSION_STRING} > /synapse/SYNAPSE_VERSION; \ + echo ${SYNAPSE_VERSION_STRING} > /synapse/synapse/SYNAPSE_VERSION; \ fi # Repeat of earlier build argument declaration, as this is a new build stage. diff --git a/pyproject.toml b/pyproject.toml index 2b0b5e7ee1a..4cb962feb5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -416,7 +416,7 @@ manifest-path = "rust/Cargo.toml" module-name = "synapse.synapse_rust" python-source = "." include = [ - { path = "SYNAPSE_VERSION", format = [ + { path = "synapse/SYNAPSE_VERSION", format = [ "sdist", "wheel", ] }, From 5d1a25fcd24caf4c7f8651377957a3269c38c706 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 20 Feb 2026 10:10:43 -0600 Subject: [PATCH 15/32] Read from file --- synapse/util/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/synapse/util/__init__.py b/synapse/util/__init__.py index 23319743cc8..6126fd9cc84 100644 --- a/synapse/util/__init__.py +++ b/synapse/util/__init__.py @@ -80,6 +80,14 @@ def log_failure( # TODO: Read from SYNAPSE_VERSION file if it exists SYNAPSE_VERSION = get_distribution_version_string("matrix-synapse", __file__) +import importlib.resources as importlib_resources + +try: + version_text = importlib_resources.read_text("synapse", "SYNAPSE_VERSION") + print(f"asdf {version_text}") +except FileNotFoundError: + pass + class ExceptionBundle(Exception): # A poor stand-in for something like Python 3.11's ExceptionGroup. From 4b33f3f1f6ee7d772d4e5d4b3d84a3b2f8c33e0d Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 20 Feb 2026 10:16:08 -0600 Subject: [PATCH 16/32] Remove `SYNAPSE_VERSION` file strategy --- docker/Dockerfile | 4 ---- pyproject.toml | 4 ---- synapse/util/__init__.py | 8 -------- 3 files changed, 16 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index e3ffc3db0a0..3eb062d3297 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -104,10 +104,6 @@ COPY synapse /synapse/synapse/ COPY rust /synapse/rust/ # ... and what we need to `pip install`. COPY pyproject.toml README.rst build_rust.py Cargo.toml Cargo.lock /synapse/ -# Create a `SYNAPSE_VERSION` file if `SYNAPSE_VERSION_STRING` was specified -RUN if [ -n "${SYNAPSE_VERSION_STRING}" ]; then \ - echo ${SYNAPSE_VERSION_STRING} > /synapse/synapse/SYNAPSE_VERSION; \ - fi # Repeat of earlier build argument declaration, as this is a new build stage. ARG TEST_ONLY_IGNORE_POETRY_LOCKFILE diff --git a/pyproject.toml b/pyproject.toml index 4cb962feb5f..8073f8ec44f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -416,10 +416,6 @@ manifest-path = "rust/Cargo.toml" module-name = "synapse.synapse_rust" python-source = "." include = [ - { path = "synapse/SYNAPSE_VERSION", format = [ - "sdist", - "wheel", - ] }, { path = "AUTHORS.rst", format = "sdist" }, { path = "book.toml", format = "sdist" }, { path = "changelog.d/**/*", format = "sdist" }, diff --git a/synapse/util/__init__.py b/synapse/util/__init__.py index 6126fd9cc84..23319743cc8 100644 --- a/synapse/util/__init__.py +++ b/synapse/util/__init__.py @@ -80,14 +80,6 @@ def log_failure( # TODO: Read from SYNAPSE_VERSION file if it exists SYNAPSE_VERSION = get_distribution_version_string("matrix-synapse", __file__) -import importlib.resources as importlib_resources - -try: - version_text = importlib_resources.read_text("synapse", "SYNAPSE_VERSION") - print(f"asdf {version_text}") -except FileNotFoundError: - pass - class ExceptionBundle(Exception): # A poor stand-in for something like Python 3.11's ExceptionGroup. From e8c5badaa691b0b20ce4a4b67ac9f8fb02971d5d Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 20 Feb 2026 10:23:59 -0600 Subject: [PATCH 17/32] Read from environment variable --- docker/Dockerfile | 14 ++++++++------ synapse/util/__init__.py | 5 ++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 3eb062d3297..2e4287e062c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -67,12 +67,6 @@ RUN --mount=type=cache,target=/root/.cache/uv \ ### FROM ghcr.io/astral-sh/uv:python${PYTHON_VERSION}-${DEBIAN_VERSION} AS builder -# If specified, Synapse will use this as the version string in the app. -# -# This can be useful to capture the git info of the build as `.git/` won't be be -# available in the Docker image for Synapse to generate from. -ARG SYNAPSE_VERSION_STRING - # This silences a warning as uv isn't able to do hardlinks between its cache # (mounted as --mount=type=cache) and the target directory. ENV UV_LINK_MODE=copy @@ -177,6 +171,14 @@ FROM docker.io/library/python:${PYTHON_VERSION}-slim-${DEBIAN_VERSION} ARG TARGETARCH +# If specified, Synapse will use this as the version string in the app. +# +# This can be useful to capture the git info of the build as `.git/` won't be be +# available in the Docker image for Synapse to generate from. +ARG SYNAPSE_VERSION_STRING +# Pass it through to Synapse as an environment variable. +ENV SYNAPSE_VERSION_STRING=${SYNAPSE_VERSION_STRING} + LABEL org.opencontainers.image.url='https://github.com/element-hq/synapse' LABEL org.opencontainers.image.documentation='https://element-hq.github.io/synapse/latest/' LABEL org.opencontainers.image.source='https://github.com/element-hq/synapse.git' diff --git a/synapse/util/__init__.py b/synapse/util/__init__.py index 23319743cc8..14cc0454ded 100644 --- a/synapse/util/__init__.py +++ b/synapse/util/__init__.py @@ -19,6 +19,7 @@ # # +import os import collections.abc import logging import typing @@ -78,7 +79,9 @@ def log_failure( # times. # # TODO: Read from SYNAPSE_VERSION file if it exists -SYNAPSE_VERSION = get_distribution_version_string("matrix-synapse", __file__) +SYNAPSE_VERSION = os.getenv( + "SYNAPSE_VERSION_STRING" +) or get_distribution_version_string("matrix-synapse", __file__) class ExceptionBundle(Exception): From 944611cb04701340d6b229c5b8f1153e63cad7dd Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 20 Feb 2026 10:25:13 -0600 Subject: [PATCH 18/32] Update docstring --- synapse/util/__init__.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/synapse/util/__init__.py b/synapse/util/__init__.py index 14cc0454ded..5d3337ddf96 100644 --- a/synapse/util/__init__.py +++ b/synapse/util/__init__.py @@ -75,13 +75,15 @@ def log_failure( return None -# Version string with git info. Computed here once so that we don't invoke git multiple -# times. -# -# TODO: Read from SYNAPSE_VERSION file if it exists SYNAPSE_VERSION = os.getenv( "SYNAPSE_VERSION_STRING" ) or get_distribution_version_string("matrix-synapse", __file__) +""" +Version string with git info. + +This can be overriden via `SYNAPSE_VERSION_STRING` or is computed here once so that we +don't invoke git multiple times. +""" class ExceptionBundle(Exception): From ddfbbf8b185d265cedc30a7dffeb65ffa2d5fc35 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 20 Feb 2026 10:26:27 -0600 Subject: [PATCH 19/32] Fix lints --- .../tests/synapse_version_check_test.go | 61 ++++++++++++------- synapse/util/__init__.py | 2 +- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/complement/tests/synapse_version_check_test.go b/complement/tests/synapse_version_check_test.go index 94668cfe2e0..4154ecce775 100644 --- a/complement/tests/synapse_version_check_test.go +++ b/complement/tests/synapse_version_check_test.go @@ -32,7 +32,7 @@ func TestSynapseVersion(t *testing.T) { deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) - unauthedClient := deployment.UnauthenticatedClient(t, "hs1"); + unauthedClient := deployment.UnauthenticatedClient(t, "hs1") // Sanity check that the version of Synapse used in the `COMPLEMENT_BASE_IMAGE` // matches the same git commit we have checked out. This ensures that the image being @@ -61,12 +61,12 @@ func TestSynapseVersion(t *testing.T) { ) // Assemble our checkout details into a `SynapseVersion` we can easily compare with - checkoutSynapseVersion := SynapseVersion { + checkoutSynapseVersion := SynapseVersion{ Version: pyprojectVersion, - Branch: gitBranch, - Tag: gitTag, - Commit: gitCommit, - Dirty: gitDirty, + Branch: gitBranch, + Tag: gitTag, + Commit: gitCommit, + Dirty: gitDirty, } // Find the version details of the Synapse instance deployed from the Docker image @@ -78,7 +78,10 @@ func TestSynapseVersion(t *testing.T) { }, }) rawSynapseVersionString := gjson.GetBytes(body, "server.version").Str - t.Logf("Synapse version string from federation version endpoint: %s", rawSynapseVersionString) + t.Logf( + "Synapse version string from federation version endpoint: %s", + rawSynapseVersionString, + ) synapseVersion, err := parseSynapseVersionString(rawSynapseVersionString) if err != nil { t.Fatalf("Failed to parse Synapse version string: %v", err) @@ -87,18 +90,19 @@ func TestSynapseVersion(t *testing.T) { // Compare if !reflect.DeepEqual(synapseVersion, checkoutSynapseVersion) { t.Fatalf( - "Synapse version in the checkout doesn't match the Synapse version that Complement is running. " + - "From the Checkout: %+v, From the Complement image: %+v\n\n" + - "If this test fails, it probably means that Complement is using an image that " + - "doesn't encompass the changes you have checked out (unexpected). We want to yell " + - "loudly and point out what's wrong instead of silently letting your PR's pass " + - "without actually being tested.", checkoutSynapseVersion, synapseVersion, + "Synapse version in the checkout doesn't match the Synapse version that Complement is running. "+ + "From the Checkout: %+v, From the Complement image: %+v\n\n"+ + "If this test fails, it probably means that Complement is using an image that "+ + "doesn't encompass the changes you have checked out (unexpected). We want to yell "+ + "loudly and point out what's wrong instead of silently letting your PR's pass "+ + "without actually being tested.", + checkoutSynapseVersion, + synapseVersion, ) } }) } - // Helper function to get `version` from pyproject.toml func getVersionFromPyproject(t *testing.T) string { // Read pyproject.toml file @@ -139,7 +143,11 @@ func runGitCommand(t *testing.T, commandPieces []string) string { cmd := exec.Command(commandPieces[0], commandPieces[1:]...) output, err := cmd.Output() if err != nil { - t.Logf("runGitCommand: failed to run command (%s) (this may be expected depending on the command): %v", strings.Join(commandPieces, " "), err) + t.Logf( + "runGitCommand: failed to run command (%s) (this may be expected depending on the command): %v", + strings.Join(commandPieces, " "), + err, + ) return "" } @@ -215,11 +223,11 @@ func parseSynapseVersionString( } return &SynapseVersion{ - Version: version, - Branch: branch, - Tag: tag, - Commit: commit, - Dirty: dirty, + Version: version, + Branch: branch, + Tag: tag, + Commit: commit, + Dirty: dirty, }, nil } @@ -276,11 +284,20 @@ func TestParseSynapseVersionString(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { got, err := parseSynapseVersionString(testCase.input) if err != nil { - t.Errorf("parseSynapseVersionString(\"%s\") failed to parse input, error: %v", testCase.input, err) + t.Errorf( + "parseSynapseVersionString(\"%s\") failed to parse input, error: %v", + testCase.input, + err, + ) } if !reflect.DeepEqual(got, testCase.expected) { - t.Errorf("parseSynapseVersionString(\"%s\") got %v, want %v", testCase.input, got, testCase.expected) + t.Errorf( + "parseSynapseVersionString(\"%s\") got %v, want %v", + testCase.input, + got, + testCase.expected, + ) } }) } diff --git a/synapse/util/__init__.py b/synapse/util/__init__.py index 5d3337ddf96..997998312c2 100644 --- a/synapse/util/__init__.py +++ b/synapse/util/__init__.py @@ -19,9 +19,9 @@ # # -import os import collections.abc import logging +import os import typing from typing import ( Iterator, From b7bd4beef19c18fb0e6ad8a594ac6cceba277373 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 20 Feb 2026 12:11:16 -0600 Subject: [PATCH 20/32] Simplify test --- .../tests/synapse_version_check_test.go | 251 ++---------------- 1 file changed, 24 insertions(+), 227 deletions(-) diff --git a/complement/tests/synapse_version_check_test.go b/complement/tests/synapse_version_check_test.go index 4154ecce775..03565813825 100644 --- a/complement/tests/synapse_version_check_test.go +++ b/complement/tests/synapse_version_check_test.go @@ -13,12 +13,8 @@ package synapse_tests import ( - "fmt" "net/http" - "os" "os/exec" - "reflect" - "regexp" "strings" "testing" @@ -46,29 +42,18 @@ func TestSynapseVersion(t *testing.T) { // loudly and point out what's wrong instead of silently letting your PR's pass // without actually being tested. t.Run("Synapse version matches current git checkout", func(t *testing.T) { - // Pull the `version` from `pyproject.toml`. - pyprojectVersion := getVersionFromPyproject(t) - - // Get the details of the current git checkout - // - // Commands match https://github.com/matrix-org/matrix-python-common/blob/4084b21af839c50f775447d02ca4f1854e2e6191/src/matrix_common/versionstring.py#L87C45-L95 - gitBranch := runGitCommand(t, []string{"git", "rev-parse", "--abbrev-ref", "HEAD"}) - gitTag := runGitCommand(t, []string{"git", "describe", "--exact-match"}) - gitCommit := runGitCommand(t, []string{"git", "rev-parse", "--short", "HEAD"}) - gitDirty := strings.HasSuffix( - runGitCommand(t, []string{"git", "describe", "--dirty=-this_is_a_dirty_checkout"}), - "-this_is_a_dirty_checkout", + // Get the Synapse version details of the current git checkout + checkoutSynapseVersion := runCommand( + t, + []string{ + "poetry", + "run", + "python", + "-c", + "from synapse.util import SYNAPSE_VERSION; print(SYNAPSE_VERSION)", + }, ) - // Assemble our checkout details into a `SynapseVersion` we can easily compare with - checkoutSynapseVersion := SynapseVersion{ - Version: pyprojectVersion, - Branch: gitBranch, - Tag: gitTag, - Commit: gitCommit, - Dirty: gitDirty, - } - // Find the version details of the Synapse instance deployed from the Docker image res := unauthedClient.MustDo(t, "GET", []string{"_matrix", "federation", "v1", "version"}) body := must.MatchResponse(t, res, match.HTTPResponse{ @@ -82,223 +67,35 @@ func TestSynapseVersion(t *testing.T) { "Synapse version string from federation version endpoint: %s", rawSynapseVersionString, ) - synapseVersion, err := parseSynapseVersionString(rawSynapseVersionString) - if err != nil { - t.Fatalf("Failed to parse Synapse version string: %v", err) - } - // Compare - if !reflect.DeepEqual(synapseVersion, checkoutSynapseVersion) { - t.Fatalf( - "Synapse version in the checkout doesn't match the Synapse version that Complement is running. "+ - "From the Checkout: %+v, From the Complement image: %+v\n\n"+ - "If this test fails, it probably means that Complement is using an image that "+ - "doesn't encompass the changes you have checked out (unexpected). We want to yell "+ - "loudly and point out what's wrong instead of silently letting your PR's pass "+ - "without actually being tested.", - checkoutSynapseVersion, - synapseVersion, - ) - } + must.Equal( + t, + rawSynapseVersionString, + checkoutSynapseVersion, + "Synapse version in the checkout doesn't match the Synapse version that Complement is running. "+ + "If this test fails, it probably means that Complement is using an image that "+ + "doesn't encompass the changes you have checked out (unexpected). We want to yell "+ + "loudly and point out what's wrong instead of silently letting your PR's pass "+ + "without actually being tested.", + ) }) } -// Helper function to get `version` from pyproject.toml -func getVersionFromPyproject(t *testing.T) string { - // Read pyproject.toml file - // - // Log the current working directory so it's easier to understand what's going on here - cwd, err := os.Getwd() - if err != nil { - t.Fatalf("getVersionFromPyproject: failed to get current working directory: %v", err) - } - t.Logf("getVersionFromPyproject: currenty working directory is %s", cwd) - // This path is relative to this file - fileContents, err := os.ReadFile("../../pyproject.toml") - if err != nil { - t.Fatalf("getVersionFromPyproject: failed to read `pyproject.toml`: %v", err) - } - - // Instead of parsing toml, we just do a dirty find of the first `version = "1.147.1"` - re := regexp.MustCompile( - `(?m)^version = "(?P.*?)"\s?$`, - ) - match := re.FindStringSubmatch(string(fileContents)) - if len(match) == 0 { - t.Fatalf("getVersionFromPyproject: Unable to find `version` in `pyproject.toml`") - } - - return match[re.SubexpIndex("Version")] -} - -// runGitCommand will run the given git command and return the stdout (whitespace +// runCommand will run the given command and return the stdout (whitespace // trimmed). -// -// Errors will be logged but this function will just return a blank string (for -// ease-of-use in the tests) -func runGitCommand(t *testing.T, commandPieces []string) string { +func runCommand(t *testing.T, commandPieces []string) string { t.Helper() // Then run our actual command cmd := exec.Command(commandPieces[0], commandPieces[1:]...) output, err := cmd.Output() if err != nil { - t.Logf( - "runGitCommand: failed to run command (%s) (this may be expected depending on the command): %v", + t.Fatalf( + "runCommand: failed to run command (%s): %v", strings.Join(commandPieces, " "), err, ) - return "" } return strings.TrimSpace(string(output)) } - -type SynapseVersion struct { - // Synapse version - Version string - // git branch - Branch string - // git tag - Tag string - // git commit - Commit string - // Whether the git repo has uncommitted changes in it - Dirty bool -} - -// parseSynapseVersionString parses Synapse version strings (from -// `GET /_matrix/federation/v1/version`) in the format: -// -// - "1.147.1" -// - "1.147.1 (b=develop,b80774efb2)" -// - "1.147.1 (b=develop,b80774efb2,dirty)" -// - "1.147.1 (b=HEAD,t=v1.147.1,7ff8687653)" -// -// See -// https://github.com/matrix-org/matrix-python-common/blob/4084b21af839c50f775447d02ca4f1854e2e6191/src/matrix_common/versionstring.py -// for how Synapse generates these version strings. -func parseSynapseVersionString( - synapseVersionString string, -) (*SynapseVersion, error) { - // We're trying to separate "1.147.1" and "(...)" - parts := strings.SplitN(synapseVersionString, " ", 2) - - version := parts[0] - gitString := "" - if len(parts) == 2 { - gitString = parts[1] - // Remove the surrounding parenthesis (...) - gitString = strings.TrimPrefix(gitString, "(") - gitString = strings.TrimSuffix(gitString, ")") - } - - branch := "" - tag := "" - commit := "" - dirty := false - gitParts := strings.Split(gitString, ",") - - // Go through piece by piece and try to match it up as best as possible. The pieces - // should always be in order (branch, tag, commit, dirty) according to the source code - // that generates them. - for _, gitPart := range gitParts { - // Match the most specific key=value pairs first - if branch == "" && strings.HasPrefix(gitPart, "b=") { - branch = strings.TrimPrefix(gitPart, "b=") - } else if tag == "" && strings.HasPrefix(gitPart, "t=") { - tag = strings.TrimPrefix(gitPart, "t=") - } else { - // Otherwise, we have to match remaining the bare pieces. - // - // The first bare piece should be the commit - if commit == "" { - commit = gitPart - } else if gitPart == "dirty" { - dirty = true - } else { - return nil, fmt.Errorf("parseSynapseVersionString: Unexpected format of git details (%s)", synapseVersionString) - } - } - } - - return &SynapseVersion{ - Version: version, - Branch: branch, - Tag: tag, - Commit: commit, - Dirty: dirty, - }, nil -} - -// Sanity check that our `parseSynapseVersionString` utility works as expected -func TestParseSynapseVersionString(t *testing.T) { - testCases := []struct { - name string - input string - expected *SynapseVersion - wantErr bool - }{ - { - name: "simple version", - input: "1.147.1", - expected: &SynapseVersion{ - Version: "1.147.1", - Dirty: false, - }, - }, - { - name: "version with branch and commit", - input: "1.147.1 (b=develop,b80774efb2)", - expected: &SynapseVersion{ - Version: "1.147.1", - Branch: "develop", - Commit: "b80774efb2", - Dirty: false, - }, - }, - { - name: "version with branch, commit, and dirty", - input: "1.147.1 (b=develop,b80774efb2,dirty)", - expected: &SynapseVersion{ - Version: "1.147.1", - Branch: "develop", - Commit: "b80774efb2", - Dirty: true, - }, - }, - { - name: "version with HEAD branch, tag, and commit", - input: "1.147.1 (b=HEAD,t=v1.147.1,7ff8687653)", - expected: &SynapseVersion{ - Version: "1.147.1", - Branch: "HEAD", - Tag: "v1.147.1", - Commit: "7ff8687653", - Dirty: false, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - got, err := parseSynapseVersionString(testCase.input) - if err != nil { - t.Errorf( - "parseSynapseVersionString(\"%s\") failed to parse input, error: %v", - testCase.input, - err, - ) - } - - if !reflect.DeepEqual(got, testCase.expected) { - t.Errorf( - "parseSynapseVersionString(\"%s\") got %v, want %v", - testCase.input, - got, - testCase.expected, - ) - } - }) - } -} From c7cb45df8accd139663fce5b4550a91a94417659 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 2 Mar 2026 10:37:40 -0600 Subject: [PATCH 21/32] Better text --- synapse/util/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/synapse/util/__init__.py b/synapse/util/__init__.py index 997998312c2..6c36ae6c09e 100644 --- a/synapse/util/__init__.py +++ b/synapse/util/__init__.py @@ -81,8 +81,8 @@ def log_failure( """ Version string with git info. -This can be overriden via `SYNAPSE_VERSION_STRING` or is computed here once so that we -don't invoke git multiple times. +This can be overriden via the `SYNAPSE_VERSION_STRING` environment variable or is +computed here once so that we don't invoke git multiple times. """ From 107066c17a4660e25da9d36b4dd3bc5c0b64d454 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 4 Mar 2026 20:13:42 -0600 Subject: [PATCH 22/32] `PR's` -> `PRs` See https://github.com/element-hq/synapse/pull/19476#discussion_r2877896802 --- complement/tests/synapse_version_check_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/complement/tests/synapse_version_check_test.go b/complement/tests/synapse_version_check_test.go index 03565813825..790a6a40ce9 100644 --- a/complement/tests/synapse_version_check_test.go +++ b/complement/tests/synapse_version_check_test.go @@ -39,7 +39,7 @@ func TestSynapseVersion(t *testing.T) { // // If this test fails, it probably means that Complement is using an image that // doesn't encompass the changes you have checked out (unexpected). We want to yell - // loudly and point out what's wrong instead of silently letting your PR's pass + // loudly and point out what's wrong instead of silently letting your PRs pass // without actually being tested. t.Run("Synapse version matches current git checkout", func(t *testing.T) { // Get the Synapse version details of the current git checkout @@ -75,7 +75,7 @@ func TestSynapseVersion(t *testing.T) { "Synapse version in the checkout doesn't match the Synapse version that Complement is running. "+ "If this test fails, it probably means that Complement is using an image that "+ "doesn't encompass the changes you have checked out (unexpected). We want to yell "+ - "loudly and point out what's wrong instead of silently letting your PR's pass "+ + "loudly and point out what's wrong instead of silently letting your PRs pass "+ "without actually being tested.", ) }) From 0b5cb91d7e0044f8d5ee731d638f0887dcddad34 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 4 Mar 2026 20:15:17 -0600 Subject: [PATCH 23/32] Remove double `be` typo See https://github.com/element-hq/synapse/pull/19476#discussion_r2877896822 --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 2e4287e062c..59771ae88fe 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -173,7 +173,7 @@ ARG TARGETARCH # If specified, Synapse will use this as the version string in the app. # -# This can be useful to capture the git info of the build as `.git/` won't be be +# This can be useful to capture the git info of the build as `.git/` won't be # available in the Docker image for Synapse to generate from. ARG SYNAPSE_VERSION_STRING # Pass it through to Synapse as an environment variable. From 3f7f4f5a99e78e91e4942f312f215596f04f9f23 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 4 Mar 2026 20:16:05 -0600 Subject: [PATCH 24/32] Fix `overridden` typo See https://github.com/element-hq/synapse/pull/19476#discussion_r2877896831 --- synapse/util/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/util/__init__.py b/synapse/util/__init__.py index 6c36ae6c09e..f2898de0b8e 100644 --- a/synapse/util/__init__.py +++ b/synapse/util/__init__.py @@ -81,7 +81,7 @@ def log_failure( """ Version string with git info. -This can be overriden via the `SYNAPSE_VERSION_STRING` environment variable or is +This can be overridden via the `SYNAPSE_VERSION_STRING` environment variable or is computed here once so that we don't invoke git multiple times. """ From b1a651e8bace342a0be99bfe1f9eb0fe6b4654c2 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 4 Mar 2026 20:23:54 -0600 Subject: [PATCH 25/32] Remove `egg-info` to get accurate Synapse version string See https://github.com/element-hq/synapse/pull/19476#discussion_r2834054637 --- scripts-dev/complement.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts-dev/complement.sh b/scripts-dev/complement.sh index 6cd88290113..c1493e822d3 100755 --- a/scripts-dev/complement.sh +++ b/scripts-dev/complement.sh @@ -192,6 +192,10 @@ main() { $CONTAINER_RUNTIME run --rm -v $editable_mount --entrypoint 'cp' complement-synapse-editable -- /synapse_rust.abi3.so.bak /editable-src/synapse/synapse_rust.abi3.so else + # We remove the `egg-info` as it can contain outdated information which won't line + # up with our current reality. + rm -rf matrix_synapse.egg-info/ + # Figure out the Synapse version string in our current checkout synapse_version_string="$(poetry run python -c 'from synapse.util import SYNAPSE_VERSION; print(SYNAPSE_VERSION)')" # Build the base Synapse image from the local checkout From f2c92ec2607e9662cd87325ca0c0cebeae301622 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 4 Mar 2026 20:36:45 -0600 Subject: [PATCH 26/32] Update CI --- .github/workflows/tests.yml | 68 +++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d61683df8b3..ae884bda85b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -707,28 +707,52 @@ jobs: cache-dependency-path: complement/go.sum go-version-file: complement/go.mod - # - name: Run Complement Tests - # id: run_complement_tests - # # -p=1: We're using `-p 1` to force the test packages to run serially as GHA boxes - # # are underpowered and don't like running tons of Synapse instances at once. - # # -json: Output JSON format so that gotestfmt can parse it. - # # - # # tee /tmp/gotest-complement.log: We tee the output to a file so that we can re-process it - # # later on for better formatting with gotestfmt. But we still want the command - # # to output to the terminal as it runs so we can see what's happening in - # # real-time. - # run: | - # set -o pipefail - # COMPLEMENT_DIR=`pwd`/complement synapse/scripts-dev/complement.sh -p 1 -json 2>&1 | tee /tmp/gotest-complement.log - # shell: bash - # env: - # POSTGRES: ${{ (matrix.database == 'Postgres') && 1 || '' }} - # WORKERS: ${{ (matrix.arrangement == 'workers') && 1 || '' }} - - # - name: Formatted Complement test logs - # # Always run this step if we attempted to run the Complement tests. - # if: always() && steps.run_complement_tests.outcome != 'skipped' - # run: cat /tmp/gotest-complement.log | gotestfmt -hide "successful-downloads,empty-packages" + # Run the image sanity check test first as this is the first thing we want to know + # about (are we actually testing what we expect?) and we don't want to debug + # downstream failures (wild goose chase). + - name: Sanity check Complement image + id: run_sanity_check_complement_image_test + # -p=1: We're using `-p 1` to force the test packages to run serially as GHA boxes + # are underpowered and don't like running tons of Synapse instances at once. + # -json: Output JSON format so that gotestfmt can parse it. + # + # tee /tmp/gotest-complement.log: We tee the output to a file so that we can re-process it + # later on for better formatting with gotestfmt. But we still want the command + # to output to the terminal as it runs so we can see what's happening in + # real-time. + run: | + set -o pipefail + COMPLEMENT_DIR=`pwd`/complement synapse/scripts-dev/complement.sh -p 1 -json -run 'TestSynapseVersion/Synapse_version_matches_current_git_checkout' 2>&1 | tee /tmp/gotest-complement.log + shell: bash + env: + POSTGRES: ${{ (matrix.database == 'Postgres') && 1 || '' }} + WORKERS: ${{ (matrix.arrangement == 'workers') && 1 || '' }} + # We don't run `gotestfmt` for the sanity check step as it's only one test so + # there is nothing to organize. And the extra step is more noise to click + # through and grok. If this step fails, it will be obvious still. + + - name: Run Complement Tests + id: run_complement_tests + # -p=1: We're using `-p 1` to force the test packages to run serially as GHA boxes + # are underpowered and don't like running tons of Synapse instances at once. + # -json: Output JSON format so that gotestfmt can parse it. + # + # tee /tmp/gotest-complement.log: We tee the output to a file so that we can re-process it + # later on for better formatting with gotestfmt. But we still want the command + # to output to the terminal as it runs so we can see what's happening in + # real-time. + run: | + set -o pipefail + COMPLEMENT_DIR=`pwd`/complement synapse/scripts-dev/complement.sh -p 1 -json 2>&1 | tee /tmp/gotest-complement.log + shell: bash + env: + POSTGRES: ${{ (matrix.database == 'Postgres') && 1 || '' }} + WORKERS: ${{ (matrix.arrangement == 'workers') && 1 || '' }} + + - name: Formatted Complement test logs + # Always run this step if we attempted to run the Complement tests. + if: always() && steps.run_complement_tests.outcome != 'skipped' + run: cat /tmp/gotest-complement.log | gotestfmt -hide "successful-downloads,empty-packages" - name: Run in-repo Complement Tests id: run_in_repo_complement_tests From 7bdc821caff7aef9a7a359bd213dac8210885d4b Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 5 Mar 2026 12:19:34 -0600 Subject: [PATCH 27/32] Kick CI again From 296ba3286dbc4849c0eda5f9a0253eb30efc9d66 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 5 Mar 2026 13:45:37 -0600 Subject: [PATCH 28/32] Make Poetry (version 2.2.1) Usage: command [options] [arguments] Options: -h, --help Display help for the given command. When no command is given display help for the list command. -q, --quiet Do not output any message. -V, --version Display this application version. --ansi Force ANSI output. --no-ansi Disable ANSI output. -n, --no-interaction Do not ask any interactive question. --no-plugins Disables plugins. --no-cache Disables Poetry source caches. -P, --project=PROJECT Specify another path as the project root. All command-line arguments will be resolved relative to the current working directory. -C, --directory=DIRECTORY The working directory for the Poetry command (defaults to the current working directory). All command-line arguments will be resolved relative to the given directory. -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug. Available commands: about Shows information about Poetry. add Adds a new dependency to pyproject.toml and installs it. build Builds a package, as a tarball and a wheel by default. check Validates the content of the pyproject.toml file and its consistency with the poetry.lock file. config Manages configuration settings. export Exports the lock file to alternative formats. help Displays help for a command. init Creates a basic pyproject.toml file in the current directory. install Installs the project dependencies. list Lists commands. lock Locks the project dependencies. new Creates a new Python project at . publish Publishes a package to a remote repository. remove Removes a package from the project dependencies. run Runs a command in the appropriate environment. search Searches for packages on remote repositories. show Shows information about packages. sync Update the project's environment according to the lockfile. update Update the dependencies as according to the pyproject.toml file. version Shows the version of the project or bumps it when a valid bump rule is provided. cache cache clear Clears a Poetry cache by name. cache list List Poetry's caches. debug debug info Shows debug information. debug resolve Debugs dependency resolution. debug tags Shows compatible tags for your project's current active environment. env env activate Print the command to activate a virtual environment. env info Displays information about the current environment. env list Lists all virtualenvs associated with the current project. env remove Remove virtual environments associated with the project. env use Activates or creates a new virtualenv for the current project. python python install Install the specified Python version from the Python Standalone Builds project. (experimental feature) python list Shows Python versions available for this environment. (experimental feature) python remove Remove the specified Python version if managed by Poetry. (experimental feature) self self add Add additional packages to Poetry's runtime environment. self install Install locked packages (incl. addons) required by this Poetry installation. self lock Lock the Poetry installation's system requirements. self remove Remove additional packages from Poetry's runtime environment. self show Show packages from Poetry's runtime environment. self show plugins Shows information about the currently installed plugins. self sync Sync Poetry's own environment according to the locked packages (incl. addons) required by this Poetry installation. self update Updates Poetry to the latest version. source source add Add source configuration for project. source remove Remove source configured for the project. source show Show information about sources configured for the project. available --- .github/workflows/tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ae884bda85b..f943ce280be 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -707,6 +707,11 @@ jobs: cache-dependency-path: complement/go.sum go-version-file: complement/go.mod + # We use `poetry` in `complement.sh` + - uses: matrix-org/setup-python-poetry@5bbf6603c5c930615ec8a29f1b5d7d258d905aa4 # v2.0.0 + with: + poetry-version: "2.1.1" + # Run the image sanity check test first as this is the first thing we want to know # about (are we actually testing what we expect?) and we don't want to debug # downstream failures (wild goose chase). From 957d5c369d74c27003958fa05b6e7ee316dfb5f6 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 5 Mar 2026 13:58:14 -0600 Subject: [PATCH 29/32] Point to Synapse checkout --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f943ce280be..3ad1d816cd4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -711,6 +711,7 @@ jobs: - uses: matrix-org/setup-python-poetry@5bbf6603c5c930615ec8a29f1b5d7d258d905aa4 # v2.0.0 with: poetry-version: "2.1.1" + working-directory: "synapse" # Run the image sanity check test first as this is the first thing we want to know # about (are we actually testing what we expect?) and we don't want to debug From c28cd6c23dde14e0a3c9c7670a0209be426dcfe0 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 5 Mar 2026 14:03:59 -0600 Subject: [PATCH 30/32] Move closer to Synapse step --- .github/workflows/tests.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3ad1d816cd4..95e987af9cb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -699,6 +699,13 @@ jobs: toolchain: ${{ env.RUST_VERSION }} - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 + # We use `poetry` in `complement.sh` + - uses: matrix-org/setup-python-poetry@5bbf6603c5c930615ec8a29f1b5d7d258d905aa4 # v2.0.0 + with: + poetry-version: "2.1.1" + # Matches the `path` where we checkout Synapse above + working-directory: "synapse" + - name: Prepare Complement's Prerequisites run: synapse/.ci/scripts/setup_complement_prerequisites.sh @@ -707,12 +714,6 @@ jobs: cache-dependency-path: complement/go.sum go-version-file: complement/go.mod - # We use `poetry` in `complement.sh` - - uses: matrix-org/setup-python-poetry@5bbf6603c5c930615ec8a29f1b5d7d258d905aa4 # v2.0.0 - with: - poetry-version: "2.1.1" - working-directory: "synapse" - # Run the image sanity check test first as this is the first thing we want to know # about (are we actually testing what we expect?) and we don't want to debug # downstream failures (wild goose chase). From 7b199ab8b2ed678ddb2542802c240f398dbc7ed1 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 5 Mar 2026 14:13:38 -0600 Subject: [PATCH 31/32] Formatting would be nice because JSON is hard to read Alternatively, we could avoid the JSON --- .github/workflows/tests.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 95e987af9cb..4f55e4efa5d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -729,14 +729,16 @@ jobs: # real-time. run: | set -o pipefail - COMPLEMENT_DIR=`pwd`/complement synapse/scripts-dev/complement.sh -p 1 -json -run 'TestSynapseVersion/Synapse_version_matches_current_git_checkout' 2>&1 | tee /tmp/gotest-complement.log + COMPLEMENT_DIR=`pwd`/complement synapse/scripts-dev/complement.sh -p 1 -json -run 'TestSynapseVersion/Synapse_version_matches_current_git_checkout' 2>&1 | tee /tmp/gotest-sanity-check-complement.log shell: bash env: POSTGRES: ${{ (matrix.database == 'Postgres') && 1 || '' }} WORKERS: ${{ (matrix.arrangement == 'workers') && 1 || '' }} - # We don't run `gotestfmt` for the sanity check step as it's only one test so - # there is nothing to organize. And the extra step is more noise to click - # through and grok. If this step fails, it will be obvious still. + + - name: Formatted sanity check Complement test logs + # Always run this step if we attempted to run the Complement tests. + if: always() && steps.run_sanity_check_complement_image_test.outcome != 'skipped' + run: cat /tmp/gotest-sanity-check-complement.log | gotestfmt -hide "successful-downloads,empty-packages" - name: Run Complement Tests id: run_complement_tests From 5dd480511f3a33d11078c39edc3a4f4e160183e6 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 5 Mar 2026 14:20:13 -0600 Subject: [PATCH 32/32] Test is in repo --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4f55e4efa5d..95f411671d8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -729,7 +729,7 @@ jobs: # real-time. run: | set -o pipefail - COMPLEMENT_DIR=`pwd`/complement synapse/scripts-dev/complement.sh -p 1 -json -run 'TestSynapseVersion/Synapse_version_matches_current_git_checkout' 2>&1 | tee /tmp/gotest-sanity-check-complement.log + COMPLEMENT_DIR=`pwd`/complement synapse/scripts-dev/complement.sh --in-repo -p 1 -json -run 'TestSynapseVersion/Synapse_version_matches_current_git_checkout' 2>&1 | tee /tmp/gotest-sanity-check-complement.log shell: bash env: POSTGRES: ${{ (matrix.database == 'Postgres') && 1 || '' }}