@@ -253,6 +253,9 @@ jobs:
253253 ~/.cache/pypoetry/virtualenvs
254254 key : ${{ runner.os }}-python-${{ hashFiles('pyproject.toml', 'poetry.lock') }}
255255
256+ - name : Create test results directory
257+ run : mkdir -p workflow/test-results
258+
256259 - name : Install PostgreSQL client
257260 run : sudo apt-get update && sudo apt-get install -y postgresql-client
258261
@@ -457,7 +460,7 @@ jobs:
457460 process_gcloudignore : false
458461
459462 # MySQL
460- build-and-test-mysql :
463+ build-and-unit- test-mysql :
461464 runs-on : ubuntu-latest
462465 needs : [rust-env, python-env]
463466 permissions :
@@ -508,6 +511,9 @@ jobs:
508511 ~/.cache/pypoetry/virtualenvs
509512 key : ${{ runner.os }}-python-${{ hashFiles('pyproject.toml', 'poetry.lock') }}
510513
514+ - name : Create test results directory
515+ run : mkdir -p workflow/test-results
516+
511517 - name : Install MySQL client
512518 run : sudo apt-get update && sudo apt-get install -y default-mysql-client
513519
@@ -699,3 +705,284 @@ jobs:
699705 glob : " *.xml"
700706 parent : false
701707 process_gcloudignore : false
708+
709+ # Spanner
710+ build-and-unit-test-spanner :
711+ runs-on : ubuntu-latest
712+ needs : [rust-env, python-env]
713+ permissions :
714+ contents : read
715+ checks : write
716+
717+ services :
718+ spanner-emulator :
719+ image : gcr.io/cloud-spanner-emulator/emulator:1.4.0
720+ ports :
721+ - 9010:9010
722+ - 9020:9020
723+
724+ mysql :
725+ image : mysql:8.0
726+ env :
727+ MYSQL_ROOT_PASSWORD : password
728+ MYSQL_USER : test
729+ MYSQL_PASSWORD : test
730+ MYSQL_DATABASE : syncstorage
731+ ports :
732+ - 3306:3306
733+ options : >-
734+ --health-cmd="mysqladmin ping"
735+ --health-interval=10s
736+ --health-timeout=5s
737+ --health-retries=3
738+
739+ env :
740+ # The code expects a spanner URL like:
741+ SYNC_SYNCSTORAGE__DATABASE_URL : spanner://projects/test-project/instances/test-instance/databases/test-database
742+ RUST_BACKTRACE : 1
743+ RUST_TEST_THREADS : 1
744+
745+ steps :
746+ - uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
747+ with :
748+ persist-credentials : false
749+
750+ - name : Restore Rust toolchain
751+ uses : actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
752+ with :
753+ path : |
754+ ~/.rustup/toolchains
755+ ~/.rustup/update-hashes
756+ key : ${{ runner.os }}-rust-toolchain-${{ env.RUST_VERSION }}
757+
758+ - name : Restore pip and Poetry virtualenv
759+ uses : actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
760+ with :
761+ path : |
762+ ~/.cache/pip
763+ ~/.cache/pypoetry/virtualenvs
764+ key : ${{ runner.os }}-python-${{ hashFiles('pyproject.toml', 'poetry.lock') }}
765+
766+ - name : Create test results directory
767+ run : mkdir -p workflow/test-results
768+
769+ - name : Create version.json
770+ run : |
771+ printf '{"commit":"%s","version":"%s","source":"https://github.com/%s/%s","build":"%s"}\n' \
772+ "${GITHUB_SHA}" \
773+ "${GITHUB_REF_NAME}" \
774+ "${GITHUB_REPOSITORY_OWNER}" \
775+ "${GITHUB_REPOSITORY_NAME}" \
776+ "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
777+ > version.json
778+ env :
779+ GITHUB_REPOSITORY_NAME : ${{ github.event.repository.name }}
780+
781+ - name : Install cargo-nextest
782+ run : curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin
783+
784+ - name : Install cargo-llvm-cov
785+ run : cargo install --locked cargo-llvm-cov
786+
787+ - name : Build workspace (spanner feature)
788+ run : |
789+ # Build with the spanner feature so any compile-time issues surface early
790+ cargo build --workspace --no-default-features --features=syncstorage-db/spanner --features=py_verifier
791+
792+ - name : Wait for Spanner Emulator to be ready
793+ run : |
794+ echo "Waiting for Spanner emulator to be ready..."
795+ for i in {1..30}; do
796+ if curl -s http://localhost:9020/ > /dev/null 2>&1; then
797+ echo "Spanner emulator is ready (REST port 9020 responding)"
798+ break
799+ fi
800+ echo "Attempt $i/30: Spanner emulator not ready yet, waiting..."
801+ sleep 2
802+ done
803+ # Verify both ports are accessible
804+ if ! curl -s http://localhost:9020/ > /dev/null 2>&1; then
805+ echo "ERROR: Cannot connect to Spanner emulator REST API at localhost:9020"
806+ exit 1
807+ fi
808+ echo "Spanner emulator is fully ready"
809+
810+ - name : Setup Spanner schema & instance (prepare-spanner.sh)
811+ env :
812+ SYNC_SYNCSTORAGE__DATABASE_URL : spanner://projects/test-project/instances/test-instance/databases/test-database
813+ SYNC_SYNCSTORAGE__SPANNER_EMULATOR_HOST : http://localhost:9020
814+ run : |
815+ # prepare-spanner.sh uses the REST API (port 9020)
816+ scripts/prepare-spanner.sh
817+
818+ - name : Create Tokenserver database
819+ run : |
820+ mysql -u root -ppassword -h 127.0.0.1 -e 'CREATE DATABASE tokenserver;'
821+ mysql -u root -ppassword -h 127.0.0.1 -e "GRANT ALL ON tokenserver.* to 'test'@'%';"
822+
823+ - name : Run Spanner unit tests with coverage
824+ env :
825+ SYNC_SYNCSTORAGE__DATABASE_URL : spanner://projects/test-project/instances/test-instance/databases/test-database
826+ SYNC_SYNCSTORAGE__SPANNER_EMULATOR_HOST : localhost:9010
827+ SYNC_TOKENSERVER__DATABASE_URL : mysql://test:test@127.0.0.1/tokenserver
828+ SYNC_TOKENSERVER__NODE_TYPE : spanner
829+ RUST_TEST_THREADS : 1
830+ run : make spanner_test_with_coverage
831+
832+ - name : Publish Test Report
833+ uses : dorny/test-reporter@a810f9bf83f2344124a920a7a0a85a6716e791f0
834+ if : always()
835+ with :
836+ name : Spanner Unit Tests
837+ path : workflow/test-results/*.xml
838+ reporter : java-junit
839+ fail-on-error : false
840+
841+ - name : Upload test results
842+ if : always()
843+ uses : actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
844+ with :
845+ name : spanner-test-results
846+ path : workflow/test-results/
847+
848+ # Upload to GCS on master
849+ - name : Authenticate to Google Cloud
850+ if : github.ref == 'refs/heads/master'
851+ uses : google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3
852+ with :
853+ credentials_json : ${{ secrets.ETE_GCLOUD_SERVICE_KEY }}
854+
855+ - name : Upload JUnit results to GCS
856+ if : github.ref == 'refs/heads/master'
857+ uses : google-github-actions/upload-cloud-storage@c0f6160ff80057923ff50e5e567695cea181ec23 # v2
858+ with :
859+ path : workflow/test-results
860+ destination : ecosystem-test-eng-metrics/syncstorage-rs/junit
861+ glob : " *.xml"
862+ parent : false
863+ process_gcloudignore : false
864+
865+ - name : Upload coverage results to GCS
866+ if : github.ref == 'refs/heads/master'
867+ uses : google-github-actions/upload-cloud-storage@c0f6160ff80057923ff50e5e567695cea181ec23 # v2
868+ with :
869+ path : workflow/test-results
870+ destination : ecosystem-test-eng-metrics/syncstorage-rs/coverage
871+ glob : " *.json"
872+ parent : false
873+ process_gcloudignore : false
874+
875+ build-spanner-image :
876+ runs-on : ubuntu-latest
877+ needs : [rust-env, python-env]
878+ permissions :
879+ contents : read
880+ actions : write
881+
882+ steps :
883+ - uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
884+ with :
885+ persist-credentials : false
886+
887+ - name : Create version.json
888+ run : |
889+ printf '{"commit":"%s","version":"%s","source":"https://github.com/%s/%s","build":"%s"}\n' \
890+ "${GITHUB_SHA}" \
891+ "${GITHUB_REF_NAME}" \
892+ "${GITHUB_REPOSITORY_OWNER}" \
893+ "${GITHUB_REPOSITORY_NAME}" \
894+ "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
895+ > version.json
896+ env :
897+ GITHUB_REPOSITORY_NAME : ${{ github.event.repository.name }}
898+
899+ - name : Cache Docker image tar
900+ id : cache-spanner-image
901+ uses : actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
902+ with :
903+ path : /tmp/spanner-image.tar
904+ key : ${{ runner.os }}-spanner-image-${{ hashFiles('Dockerfile', 'Cargo.lock', '**/*.rs', '**/Cargo.toml', 'tools/**', 'scripts/**') }}
905+ - name : Set up Docker Buildx
906+ uses : docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
907+
908+ - name : Build Spanner Docker image (local artifact)
909+ uses : docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
910+ with :
911+ context : .
912+ push : false
913+ tags : app:build
914+ build-args : |
915+ SYNCSTORAGE_DATABASE_BACKEND=spanner
916+ MYSQLCLIENT_PKG=libmysqlclient-dev
917+ outputs : type=docker,dest=/tmp/spanner-image.tar
918+ cache-from : type=gha
919+ cache-to : type=gha,mode=max
920+
921+ - name : Upload Docker image artifact
922+ uses : actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
923+ with :
924+ name : spanner-docker-image
925+ path : /tmp/spanner-image.tar
926+ retention-days : 1
927+
928+ spanner-e2e-tests :
929+ runs-on : ubuntu-latest
930+ needs : build-spanner-image
931+ permissions :
932+ contents : read
933+ checks : write
934+
935+ steps :
936+ - uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
937+ with :
938+ persist-credentials : false
939+
940+ - name : Download Docker image
941+ uses : actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
942+ with :
943+ name : spanner-docker-image
944+ path : /tmp
945+
946+ - name : Load Docker image
947+ run : docker load --input /tmp/spanner-image.tar
948+
949+ - name : Create test results directory
950+ run : mkdir -p workflow/test-results
951+
952+ - name : Run Spanner e2e tests
953+ run : make docker_run_spanner_e2e_tests
954+ env :
955+ SYNCSTORAGE_RS_IMAGE : app:build
956+
957+ - name : Publish E2E Test Report
958+ uses : dorny/test-reporter@a810f9bf83f2344124a920a7a0a85a6716e791f0
959+ if : always()
960+ with :
961+ name : Spanner E2E Tests
962+ path : workflow/test-results/*.xml
963+ reporter : java-junit
964+ fail-on-error : false
965+
966+ - name : Upload e2e test results
967+ if : always()
968+ uses : actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
969+ with :
970+ name : spanner-e2e-test-results
971+ path : workflow/test-results/
972+
973+ # Upload to GCS on master
974+ - name : Authenticate to Google Cloud
975+ if : github.ref == 'refs/heads/master'
976+ uses : google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3
977+ with :
978+ credentials_json : ${{ secrets.ETE_GCLOUD_SERVICE_KEY }}
979+
980+ - name : Upload e2e test results to GCS
981+ if : github.ref == 'refs/heads/master'
982+ uses : google-github-actions/upload-cloud-storage@c0f6160ff80057923ff50e5e567695cea181ec23 # v2
983+ with :
984+ path : workflow/test-results
985+ destination : ecosystem-test-eng-metrics/syncstorage-rs/junit
986+ glob : " *.xml"
987+ parent : false
988+ process_gcloudignore : false
0 commit comments