diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml new file mode 100644 index 00000000..76a1d066 --- /dev/null +++ b/.github/workflows/integration.yaml @@ -0,0 +1,122 @@ +name: Integration + +# Controls when the workflow will run +on: + pull_request: + push: + branches: + - main + schedule: + - cron: "0 */6 * * *" # Every 6 hours + workflow_dispatch: + inputs: + restateCommit: + description: "restate commit" + required: false + default: "" + type: string + restateImage: + description: "restate image, superseded by restate commit" + required: false + default: "ghcr.io/restatedev/restate:main" + type: string + workflow_call: + inputs: + restateCommit: + description: "restate commit" + required: false + default: "" + type: string + restateImage: + description: "restate image, superseded by restate commit" + required: false + default: "ghcr.io/restatedev/restate:main" + type: string + +jobs: + sdk-test-suite: + if: github.repository_owner == 'restatedev' + runs-on: ubuntu-latest + name: "Features integration test (sdk-test-suite version ${{ matrix.sdk-test-suite }})" + strategy: + matrix: + sdk-test-suite: ["1.5"] + permissions: + contents: read + issues: read + checks: write + pull-requests: write + actions: read + + steps: + - uses: actions/checkout@v4 + with: + repository: restatedev/sdk-typescript + + ### Download the Restate container image, if needed + # Setup restate snapshot if necessary + # Due to https://github.com/actions/upload-artifact/issues/53 + # We must use download-artifact to get artifacts created during *this* workflow run, ie by workflow call + - name: Download restate snapshot from in-progress workflow + if: ${{ inputs.restateCommit != '' && github.event_name != 'workflow_dispatch' }} + uses: actions/download-artifact@v4 + with: + name: restate.tar + # In the workflow dispatch case where the artifact was created in a previous run, we can download as normal + - name: Download restate snapshot from completed workflow + if: ${{ inputs.restateCommit != '' && github.event_name == 'workflow_dispatch' }} + uses: dawidd6/action-download-artifact@v3 + with: + repo: restatedev/restate + workflow: ci.yml + commit: ${{ inputs.restateCommit }} + name: restate.tar + - name: Install restate snapshot + if: ${{ inputs.restateCommit != '' }} + run: | + output=$(docker load --input restate.tar) + docker tag "${output#*: }" "localhost/restatedev/restate-commit-download:latest" + docker image ls -a + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Setup sdk-test-suite + run: wget --no-verbose https://github.com/restatedev/sdk-test-suite/releases/download/v${{ matrix.sdk-test-suite }}/restate-sdk-test-suite.jar + + - name: Build Typescript test-services image + id: build + uses: docker/build-push-action@v6 + with: + context: . + file: "packages/restate-e2e-services/Dockerfile" + push: false + load: true + tags: localhost/restatedev/test-services:latest + cache-from: type=gha,scope=${{ github.workflow }} + cache-to: type=gha,mode=max,scope=${{ github.workflow }} + + # Run test suite + - name: Run test suite + env: + RESTATE_CONTAINER_IMAGE: ${{ inputs.restateCommit != '' && 'localhost/restatedev/restate-commit-download:latest' || (inputs.restateImage != '' && inputs.restateImage || 'ghcr.io/restatedev/restate:main') }} + run: java -jar restate-sdk-test-suite.jar run --report-dir=test-report --exclusions-file packages/restate-e2e-services/exclusions.yaml localhost/restatedev/test-services:latest + + # Upload logs and publish test result + - uses: actions/upload-artifact@v4 + if: always() # Make sure this is run even when test fails + with: + name: sdk-typescript-integration-test-report + path: test-report + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + files: | + test-report/*/*.xml diff --git a/packages/restate-e2e-services/Dockerfile b/packages/restate-e2e-services/Dockerfile index 9878d277..9d1bfcfa 100644 --- a/packages/restate-e2e-services/Dockerfile +++ b/packages/restate-e2e-services/Dockerfile @@ -6,6 +6,7 @@ COPY . . RUN npm install +RUN npm run proto RUN npm run build FROM node:20 as prod diff --git a/packages/restate-e2e-services/exclusions.yaml b/packages/restate-e2e-services/exclusions.yaml new file mode 100644 index 00000000..7831c238 --- /dev/null +++ b/packages/restate-e2e-services/exclusions.yaml @@ -0,0 +1 @@ +exclusions: {} diff --git a/packages/restate-e2e-services/src/app.ts b/packages/restate-e2e-services/src/app.ts index 9c2ee6ea..66f71ed8 100644 --- a/packages/restate-e2e-services/src/app.ts +++ b/packages/restate-e2e-services/src/app.ts @@ -21,6 +21,7 @@ import "./side_effect.js"; import "./workflow.js"; import "./proxy.js"; import "./test_utils.js"; +import "./kill.js"; import { REGISTRY } from "./services.js"; diff --git a/packages/restate-e2e-services/src/kill.ts b/packages/restate-e2e-services/src/kill.ts new file mode 100644 index 00000000..52531564 --- /dev/null +++ b/packages/restate-e2e-services/src/kill.ts @@ -0,0 +1,43 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate e2e tests, +// which are released under the MIT license. +// +// You can find a copy of the license in file LICENSE in the root +// directory of this repository or package, or at +// https://github.com/restatedev/e2e/blob/main/LICENSE + +import * as restate from "@restatedev/restate-sdk"; +import { REGISTRY } from "./services.js"; +import type { AwakeableHolder } from "./awakeable_holder.js"; + +const kill = restate.service({ + name: "KillTestRunner", + handlers: { + async startCallTree(ctx: restate.ObjectContext) { + await ctx.objectClient(killSingleton, "").recursiveCall(); + }, + }, +}); + +const killSingleton = restate.object({ + name: "KillTestSingleton", + handlers: { + async recursiveCall(ctx: restate.ObjectContext) { + const { id, promise } = ctx.awakeable(); + ctx + .objectSendClient({ name: "AwakeableHolder" }, "kill") + .hold(id); + await promise; + + await ctx.objectClient(killSingleton, "").recursiveCall(); + }, + + isUnlocked() { + return Promise.resolve(); + }, + }, +}); + +REGISTRY.addService(kill); +REGISTRY.addObject(killSingleton);