diff --git a/.github/workflows/docsite.yml b/.github/workflows/docsite.yml index 7efac0fa..d12678fd 100644 --- a/.github/workflows/docsite.yml +++ b/.github/workflows/docsite.yml @@ -7,7 +7,6 @@ on: branches: ['main'] push: branches: [main] - paths: [docs/**] concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref }} @@ -29,7 +28,10 @@ jobs: - name: Run go generate (builtin) run: go generate ./internal/injector/builtin - name: Build Site - run: go -C ./docs run github.com/gohugoio/hugo --minify --enableGitInfo + # Set environment to anything other than "production", as the theme we use adds SRI attributes to all CSS files, + # but datadoghq.dev is behind CloudFlare with auto-minify enabled; which breaks SRI if its minification is not + # identical to hugo's. + run: go -C ./docs run github.com/gohugoio/hugo --minify --enableGitInfo --environment=gh-pages - name: Upload Artifact uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index ba7fb5ff..47e05b67 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -72,14 +72,14 @@ jobs: needs: generate runs-on: ubuntu-latest if: always() && needs.generate.outputs.has-patch == 'true' && github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name == github.repository || github.event.pull_request.maintainer_can_modify) - permissions: - contents: write steps: - name: Checkout uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.ref }} repository: ${{ github.event.pull_request.head.repo.full_name }} + token: ${{ secrets.MUTATOR_GITHUB_TOKEN }} + persist-credentials: true - name: Download patch uses: actions/download-artifact@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..f1f5ee80 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,60 @@ +name: Release +on: + pull_request: + paths: ['internal/version/version.go'] + push: + branches: ['main'] + paths: ['internal/version/version.go'] + +jobs: + validate: + runs-on: ubuntu-latest + permissions: + contents: write # To be able to create draft releases + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup go + uses: actions/setup-go@v5 + with: + cache-dependency-path: '**/*.sum' + + # Obtains the current configured version tag from source, and verifies it is a valid tag name. + # Also checks whether the tag already exists. + - name: Determine version + id: version + run: |- + set -euo pipefail + # From https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string, with added v prefix. + VERSION_TAG_REGEX='^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$' + version=$(grep -E "${VERSION_TAG_REGEX}" <(go run . version)) + echo "tag=${version}" >> "${GITHUB_OUTPUT}" + if gh release view "${version}" --json isDraft; then + echo "exists=true" >> "${GITHUB_OUTPUT}" + else + echo "exists=false" >> "${GITHUB_OUTPUT}" + fi + env: + GH_TOKEN: ${{ github.token }} + + # If this is a pull request, and the release does not yet exist, the PR title must be "release: " + - name: 'Pull Request title must be "release: ${{ steps.version.outputs.tag }}"' + if: "github.event_name == 'pull_request' && !fromJSON(steps.version.outputs.exists) && format('release: {0}', steps.version.outputs.tag) != github.event.pull_request.title" + run: |- + echo 'Please update the PR title to "release: ${{ steps.version.outputs.tag }}" (instead of ${{ toJSON(github.event.pull_request.title) }})' + exit 1 + + # Release must not already exist (if the PR title suggests this is intended to be a release) + - name: Release ${{ steps.version.outputs.tag }} already exists + if: github.event_name == 'pull_request' && fromJSON(steps.version.outputs.exists) && startsWith(github.event.pull_request.title, 'release:') + run: |- + echo 'A release already exists for tag ${{ steps.version.outputs.tag }}. Please update to another version.' + exit 1 + + # If the release does not yet exist, create a draft release targeting this commit. + - name: Create draft release + if: github.event_name == 'push' && steps.version.outputs.exists == 'false' + run: |- + gh release create '${{ steps.version.outputs.tag }}' --draft --generate-notes --target='${{ github.sha }}' --title='${{ steps.version.outputs.tag }}' ${{ contains(steps.version.outputs.tag, '-') && '--prerelease' || '' }} + env: + GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c66059f5..e74bb200 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -52,8 +52,9 @@ jobs: cache-dependency-path: "**/*.sum" - name: Run unit tests run: |- - mkdir -p ./coverage - go test -cover -covermode=atomic -coverpkg=./... -coverprofile=coverage/unit.out -race ./... + mkdir -p coverage + go test -shuffle=on -cover -covermode=atomic -coverpkg=./... -coverprofile=${{ github.workspace }}/coverage/unit.out -race ./... + go -C _integration-tests test -shuffle=on -cover -covermode=atomic -coverpkg=./...,github.com/datadog/orchestrion/... -coverprofile=${{ github.workspace }}/coverage/integration.out -race ./... - name: Determine simple go version if: always() && github.event_name != 'merge_group' id: simple-go-version @@ -68,7 +69,7 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} flags: go${{ steps.simple-go-version.outputs.version }},${{ runner.os }},${{ runner.arch }},unit - file: ./coverage/unit.out + files: ./coverage/unit.out,./coverage/integration.out name: Unit Tests (go${{ matrix.go-version }}) integration-tests: @@ -100,52 +101,53 @@ jobs: uses: actions/setup-python@v5 with: python-version: '>=3.9 <3.13' + cache: pip + cache-dependency-path: _integration-tests/utils/agent/requirements.txt + - name: Install python dependencies + run: pip install -r _integration-tests/utils/agent/requirements.txt + - name: Build orchestrion binary + run: go build -cover -covermode=atomic -coverpkg="./..." -o="bin/orchestrion.exe" . - name: Run Integration Tests - shell: bash # PowerShell has different syntax for env. var access... + shell: bash run: |- - # Without orchestrion enabled - go -C _integration-tests test -v ./... - - # With orchestrion enabled - mkdir -p ${GOCOVERDIR} - go run -cover -covermode=atomic -coverpkg=./... . go -C _integration-tests test -v ./... + mkdir -p "${GOCOVERDIR}" + case "${{ matrix.build-mode }}" in + "DRIVER") + bin/orchestrion.exe go -C=_integration-tests test -shuffle=on ./... + ;; + "TOOLEXEC") + go -C=_integration-tests test -shuffle=on -toolexec="${{ github.workspace }}/bin/orchestrion.exe toolexec" ./... + ;; + "GOFLAGS") + export GOFLAGS="'-toolexec=${{ github.workspace }}/bin/orchestrion.exe toolexec' ${GOFLAGS}" + go -C=_integration-tests test -shuffle=on ./... + ;; + *) + echo "Unknown build mode: ${{ matrix.build-mode }}" + exit 1 + ;; + esac env: GOCOVERDIR: ${{ github.workspace }}/coverage/raw - GOCACHE: ${{ runner.temp }}/gocache - - name: Run Integration Tests - run: ./integration-tests.ps1 - env: - TESTCASE_BUILD_MODE: ${{ matrix.build-mode }} + GOFLAGS: -tags=integration,buildtag # Globally set build tags (buildtag is used by the dd-span test) - name: Consolidate coverage report - if: always() && github.event_name != 'merge_group' - shell: bash # PowerShell mkdir -p fails if the directory already exists... - run: |- - mkdir -p ./coverage - go tool covdata textfmt -i ./coverage/raw,./_integration-tests/outputs/coverage -o ./coverage/integration.out + if: github.event_name != 'merge_group' + run: go tool covdata textfmt -i ./coverage/raw -o ./coverage/integration.out - name: Determine simple go version - if: always() && github.event_name != 'merge_group' + if: github.event_name != 'merge_group' id: simple-go-version run: echo "::set-output name=version::${COMPLETE_VERSION:0:4}" shell: bash env: COMPLETE_VERSION: ${{ matrix.go-version }} - name: Upload coverage report - # We want this even if the tests failed - if: always() && github.event_name != 'merge_group' + if: github.event_name != 'merge_group' uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} flags: go${{ steps.simple-go-version.outputs.version }},${{ runner.os }},${{ runner.arch }},integration - file: ./coverage/integration.out + files: ./coverage/integration.out name: Integration Tests (go${{ matrix.go-version }}, ${{ matrix.runs-on }}, ${{ matrix.build-mode }}) - - name: Upload artifact - # We want this even if the tests failed - if: always() - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.runs-on }}-tests-${{ matrix.go-version }}-${{ matrix.build-mode }}-output - path: _integration-tests/outputs - if-no-files-found: error # That would be very unexpected # This is just a join point intended to simplify branch protection settings complete: diff --git a/README.md b/README.md index 45faf230..d8155a8d 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,9 @@ Automatic compile-time instrumentation of Go code -![Orchestrion](https://upload.wikimedia.org/wikipedia/commons/5/55/Welteorchestrion1862.jpg) - ## Overview -Orchestrion processes Go source code at compilation time and automatically inserts instrumentation. This instrumentation +[Orchestrion](https://en.wikipedia.org/wiki/Orchestrion) processes Go source code at compilation time and automatically inserts instrumentation. This instrumentation produces Datadog APM traces from the instrumented code and supports Datadog Application Security Management. Future work will include support for OpenTelemetry tracing as well. diff --git a/RELEASING.md b/RELEASING.md index 7cd42cfc..49030446 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,52 +1,33 @@ # Release process -## Overview - -The release process consists of creating a release branch, merging fixes to `main` **and** to the release branch, and releasing release candidates as things progress. Once a release candidate is stable, a final version can be released. - ## Steps -### Create the release branch and the first release candidate - -1. Checkout the repository on the correct branch and changeset (`main`). -2. Create a new release branch: `git checkout -b vX.Y`. -3. Add a tag for the first release candidate: `git tag vX.Y.Z-rc.1`. -4. Push the branch and tag. - +1. Determine the new release's version number + - Follow [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html) semantics + + Be mindful of the `v0.x.y` semantics! + - The placeholder `vX.Y.Z` is used to refer to the tag name including this version number in all + steps below +1. Check out the repository on the correct commit, which is most likely `origin/main` ```console - $ git push origin vX.Y - $ git push origin vX.Y.Z-rc.1 + $ git fetch + $ git checkout origin/main -b ${USER}/release/vX.Y.Z ``` -5. Bump the tag in the `internal/version/version.go` file in the main branch to the next minor pre-release version using the `-dev` pre-release suffix. - -### Create a release candidate after a bug fix - -**Note:** The fix must be merged to `main` and backported the release branch. - -1. Update the release branch `vX.Y` locally by pulling the bug fix merged upstream (`git fetch`, `git pull`) -2. Modify the version string in `internal/version/version.go` to the release candidate version. -3. Add a tag for the new release candidate: `git tag vX.Y.Z-rc.W`. -4. Push the branch and tag. - +1. Edit [`internal/version/version.go`](/internal/version/version.go) to set the `Tag` constant to + the new `vX.Y.Z` version +1. Commit the resulting changes ```console - $ git push origin vX.Y - $ git push origin vX.Y.Z-rc.W + $ git commit -m "release: vX.Y.Z" internal/version/version.go ``` - -### Release the final version - -1. Update the release branch `vX.Y` locally by pulling any bug fixes merged upstream (`git fetch`, `git pull`) -2. Modify the version string in `internal/version/version.go` to the final version. -3. Add a final release tag: `git tag vX.Y.Z`. -4. Push the branch and tag. - +1. Open a pull request ```console - $ git push origin vX.Y - $ git push origin vX.Y.Z-rc.W + $ gh pr create --web ``` - -5. Create a [GitHub release](https://github.com/DataDog/orchestrion/releases/new). - - Choose the version tag `vX.Y.Z` - - Set the release title to `vX.Y.Z` - - Click on `Generate release notes` for automatic release notes generation - - Click on `Publish release` +1. Get the PR reviewed by a colleague, ensure all CI passes including the _Release_ validations +1. Get the PR merged to `main` via the merge queue +1. Once merged, a draft release will automatically be created on GitHub + - Locate it on the [releases](https://github.com/DataDog/orchestrion/releases) page + - Review the release notes, and edit them if necessary: + + Remove `chore:` entries + + Fix any typos you notice +1. Once validated, publish the release on GitHub + - This automatically creates the release tag, so you're done! diff --git a/_integration-tests/go.mod b/_integration-tests/go.mod index 80abd10e..4bf42272 100644 --- a/_integration-tests/go.mod +++ b/_integration-tests/go.mod @@ -6,20 +6,21 @@ replace github.com/datadog/orchestrion => ../ require ( github.com/datadog/orchestrion v0.0.0-00010101000000-000000000000 - github.com/docker/docker v25.0.5+incompatible + github.com/dave/jennifer v1.7.0 github.com/gin-gonic/gin v1.10.0 github.com/go-chi/chi/v5 v5.1.0 github.com/go-redis/redis/v7 v7.4.1 github.com/go-redis/redis/v8 v8.11.5 github.com/gofiber/fiber/v2 v2.52.5 github.com/gomodule/redigo v1.9.2 + github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/jinzhu/gorm v1.9.16 github.com/labstack/echo/v4 v4.12.0 github.com/mattn/go-sqlite3 v1.14.22 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.31.0 - github.com/testcontainers/testcontainers-go/modules/redis v0.31.0 + github.com/testcontainers/testcontainers-go v0.32.0 + github.com/testcontainers/testcontainers-go/modules/redis v0.32.0 github.com/xlab/treeprint v1.2.0 google.golang.org/grpc v1.65.0 google.golang.org/grpc/examples v0.0.0-20230913203803-9deee9ba5f5b @@ -53,7 +54,7 @@ require ( github.com/DataDog/go-tuf v1.1.0-0.5.2 // indirect github.com/DataDog/sketches-go v1.4.5 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/hcsshim v0.11.5 // indirect github.com/alecthomas/chroma/v2 v2.14.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c // indirect @@ -103,17 +104,18 @@ require ( github.com/cli/safeexec v1.0.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect + github.com/containerd/errdefs v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/dave/dst v0.27.3 // indirect - github.com/dave/jennifer v1.7.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/disintegration/gift v1.2.1 // indirect - github.com/distribution/reference v0.5.0 // indirect + github.com/distribution/reference v0.6.0 // indirect github.com/dlclark/regexp2 v1.11.0 // indirect + github.com/docker/docker v27.0.3+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -144,7 +146,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e // indirect github.com/gohugoio/httpcache v0.7.0 // indirect - github.com/gohugoio/hugo v0.128.1 // indirect + github.com/gohugoio/hugo v0.128.2 // indirect github.com/gohugoio/hugo-goldmark-extensions/extras v0.2.0 // indirect github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.2.0 // indirect github.com/gohugoio/locales v0.14.0 // indirect @@ -156,7 +158,6 @@ require ( github.com/google/go-licenses v1.6.0 // indirect github.com/google/licenseclassifier v0.0.0-20221004142553-c1ed8fcf4bab // indirect github.com/google/s2a-go v0.1.7 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/google/wire v0.5.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.2 // indirect @@ -197,6 +198,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/hashstructure v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.1.0 // indirect @@ -269,8 +271,8 @@ require ( golang.org/x/net v0.26.0 // indirect golang.org/x/oauth2 v0.20.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/term v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/term v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.22.0 // indirect diff --git a/_integration-tests/go.sum b/_integration-tests/go.sum index 9b5b8e6b..9df9668f 100644 --- a/_integration-tests/go.sum +++ b/_integration-tests/go.sum @@ -117,8 +117,8 @@ github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpz github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/hcsshim v0.11.5 h1:haEcLNpj9Ka1gd3B3tAEs9CpE0c+1IhoL59w/exYU38= +github.com/Microsoft/hcsshim v0.11.5/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= @@ -254,8 +254,10 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= +github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= +github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= @@ -284,14 +286,14 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE= +github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -400,8 +402,8 @@ github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7 github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ= github.com/gohugoio/httpcache v0.7.0 h1:ukPnn04Rgvx48JIinZvZetBfHaWE7I01JR2Q2RrQ3Vs= github.com/gohugoio/httpcache v0.7.0/go.mod h1:fMlPrdY/vVJhAriLZnrF5QpN3BNAcoBClgAyQd+lGFI= -github.com/gohugoio/hugo v0.128.1 h1:lUWyg0nTLUqZPu2lx9rzUhsBimOKRtahOX23NmWdcUA= -github.com/gohugoio/hugo v0.128.1/go.mod h1:Fe6p5/9TZ35+272Mjj0Q5WAOvmKGNWOhaoZhoQgJCCA= +github.com/gohugoio/hugo v0.128.2 h1:VEQ5HqqCG881q1c9VBrt4NyqrSb+1SHMzoX+KzweWe4= +github.com/gohugoio/hugo v0.128.2/go.mod h1:Fe6p5/9TZ35+272Mjj0Q5WAOvmKGNWOhaoZhoQgJCCA= github.com/gohugoio/hugo-goldmark-extensions/extras v0.2.0 h1:MNdY6hYCTQEekY0oAfsxWZU1CDt6iH+tMLgyMJQh/sg= github.com/gohugoio/hugo-goldmark-extensions/extras v0.2.0/go.mod h1:oBdBVuiZ0fv9xd8xflUgt53QxW5jOCb1S+xntcN4SKo= github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.2.0 h1:PCtO5l++psZf48yen2LxQ3JiOXxaRC6v0594NeHvGZg= @@ -675,6 +677,8 @@ github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -835,10 +839,10 @@ github.com/tdewolff/parse/v2 v2.7.13/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo= github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= -github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jXjVCA27AkmbnZ+UTh3U= -github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI= -github.com/testcontainers/testcontainers-go/modules/redis v0.31.0 h1:5X6GhOdLwV86zcW8sxppJAMtsDC9u+r9tb3biBc9GKs= -github.com/testcontainers/testcontainers-go/modules/redis v0.31.0/go.mod h1:dKi5xBwy1k4u8yb3saQHu7hMEJwewHXxzbcMAuLiA6o= +github.com/testcontainers/testcontainers-go v0.32.0 h1:ug1aK08L3gCHdhknlTTwWjPHPS+/alvLJU/DRxTD/ME= +github.com/testcontainers/testcontainers-go v0.32.0/go.mod h1:CRHrzHLQhlXUsa5gXjTOfqIEJcrK5+xMDmBr/WMI88E= +github.com/testcontainers/testcontainers-go/modules/redis v0.32.0 h1:HW5Qo9qfLi5iwfS7cbXwG6qe8ybXGePcgGPEmVlVDlo= +github.com/testcontainers/testcontainers-go/modules/redis v0.32.0/go.mod h1:5kltdxVKZG0aP1iegeqKz4K8HHyP0wbkW5o84qLyMjY= github.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU= github.com/tinylib/msgp v1.1.9/go.mod h1:BCXGB54lDD8qUEPmiG0cQQUANC4IUQyB2ItS2UDlO/k= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= @@ -1147,14 +1151,14 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/_integration-tests/tests/chi.v5/main.go b/_integration-tests/tests/chi.v5/main.go index 277ae85d..1211f9c1 100644 --- a/_integration-tests/tests/chi.v5/main.go +++ b/_integration-tests/tests/chi.v5/main.go @@ -3,41 +3,81 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-present Datadog, Inc. -package main +package chiv5 import ( "context" - "log" + "fmt" "net/http" - "orchestrion/integration" + "orchestrion/integration/validator/trace" + "testing" "time" "github.com/go-chi/chi/v5" + "github.com/stretchr/testify/require" ) -func main() { +type TestCase struct { + *http.Server +} + +func (tc *TestCase) Setup(t *testing.T) { router := chi.NewRouter() + //dd:ignore - s := &http.Server{ + tc.Server = &http.Server{ Addr: "127.0.0.1:8080", Handler: router, } - router.Get("/quit", - //dd:ignore - func(w http.ResponseWriter, _ *http.Request) { - log.Println("Shutdown requested...") - defer s.Shutdown(context.Background()) - w.Write([]byte("Goodbye\n")) - }) router.Get("/", func(w http.ResponseWriter, _ *http.Request) { w.Write([]byte("Hello World!\n")) }) - integration.OnSignal(func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - s.Shutdown(ctx) - }) - log.Print(s.ListenAndServe()) + go func() { + require.ErrorIs(t, tc.Server.ListenAndServe(), http.ErrServerClosed) + }() +} + +func (tc *TestCase) Run(t *testing.T) { + resp, err := http.Get(fmt.Sprintf("http://%s/", tc.Server.Addr)) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) +} + +func (tc *TestCase) Teardown(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + require.NoError(t, tc.Server.Shutdown(ctx)) +} + +func (tc *TestCase) ExpectedTraces() trace.Spans { + return trace.Spans{ + { + // NB: Top-level span is from the HTTP Client, which is library-side instrumented. + Tags: map[string]any{ + "name": "http.request", + "resource": "GET /", + "service": "tests.test", + "type": "http", + }, + Meta: map[string]any{ + "http.url": fmt.Sprintf("http://%s/", tc.Server.Addr), + }, + Children: trace.Spans{ + { + Tags: map[string]any{ + "name": "http.request", + "resource": "GET /", + "service": "chi.router", + "type": "web", + }, + Meta: map[string]any{ + "http.url": fmt.Sprintf("http://%s/", tc.Server.Addr), + }, + }, + }, + }, + } } diff --git a/_integration-tests/tests/chi.v5/validation.json b/_integration-tests/tests/chi.v5/validation.json deleted file mode 100644 index 72208db2..00000000 --- a/_integration-tests/tests/chi.v5/validation.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "url": "http://localhost:8080", - "quit": "http://localhost:8080/quit", - "output": [ - { - "name": "http.request", - "service": "chi.router", - "resource": "GET /", - "type": "web", - "meta": { - "http.url": "http://localhost:8080/" - } - } - ] -} diff --git a/_integration-tests/tests/dd-span/main.go b/_integration-tests/tests/dd-span/main.go index 4ec4a054..1e4649a0 100644 --- a/_integration-tests/tests/dd-span/main.go +++ b/_integration-tests/tests/dd-span/main.go @@ -3,47 +3,33 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-present Datadog, Inc. -package main +package ddspan import ( "context" - "log" "net/http" - "time" + "testing" - "orchestrion/integration" + "github.com/stretchr/testify/require" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) -func main() { - mux := http.NewServeMux() - s := &http.Server{ - Addr: "127.0.0.1:8092", - Handler: mux, - } - - mux.HandleFunc("/quit", - //dd:ignore - func(w http.ResponseWriter, r *http.Request) { - log.Println("Shutdown requested...") - defer s.Shutdown(context.Background()) - w.Write([]byte("Goodbye\n")) - }) - - mux.HandleFunc("/", - //dd:ignore - func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(spanFromHttpRequest(r))) - }) - - integration.OnSignal(func() { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - s.Shutdown(ctx) - }) - - log.Print(s.ListenAndServe()) +type TestCase struct{} + +func (tc *TestCase) Setup(t *testing.T) {} + +func (tc *TestCase) Run(t *testing.T) { + span, ctx := tracer.StartSpanFromContext(context.Background(), "test.root") + defer span.Finish() + + req, err := http.NewRequestWithContext(ctx, "GET", "http://localhost:0/", nil) + require.NoError(t, err) + + spanFromHttpRequest(req) } +func (tc *TestCase) Teardown(t *testing.T) {} + //dd:span foo:bar func spanFromHttpRequest(req *http.Request) string { return tagSpecificSpan(req.Context()) diff --git a/_integration-tests/tests/dd-span/no-tag.go b/_integration-tests/tests/dd-span/no-tag.go index a8cfe44e..a5864585 100644 --- a/_integration-tests/tests/dd-span/no-tag.go +++ b/_integration-tests/tests/dd-span/no-tag.go @@ -5,9 +5,37 @@ //go:build !buildtag -package main +package ddspan -import "context" +import ( + "context" + + "orchestrion/integration/validator/trace" +) + +func (tc *TestCase) ExpectedTraces() trace.Spans { + return trace.Spans{ + { + Tags: map[string]any{ + "name": "test.root", + }, + Children: trace.Spans{ + { + Meta: map[string]any{ + "foo": "bar", + }, + Children: trace.Spans{ + { + Meta: map[string]any{ + "variant": "notag", + }, + }, + }, + }, + }, + }, + } +} //dd:span variant:notag func tagSpecificSpan(context.Context) string { diff --git a/_integration-tests/tests/dd-span/tag.go b/_integration-tests/tests/dd-span/tag.go index 20c44c91..3be1fe35 100644 --- a/_integration-tests/tests/dd-span/tag.go +++ b/_integration-tests/tests/dd-span/tag.go @@ -5,9 +5,37 @@ //go:build buildtag -package main +package ddspan -import "context" +import ( + "context" + + "orchestrion/integration/validator/trace" +) + +func (tc *TestCase) ExpectedTraces() trace.Spans { + return trace.Spans{ + { + Tags: map[string]any{ + "name": "test.root", + }, + Children: trace.Spans{ + { + Meta: map[string]any{ + "foo": "bar", + }, + Children: trace.Spans{ + { + Meta: map[string]any{ + "variant": "tag", + }, + }, + }, + }, + }, + }, + } +} //dd:span variant:tag func tagSpecificSpan(context.Context) string { diff --git a/_integration-tests/tests/dd-span/validation.json b/_integration-tests/tests/dd-span/validation.json deleted file mode 100644 index 0d3104b1..00000000 --- a/_integration-tests/tests/dd-span/validation.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "url": "http://localhost:8092", - "quit": "http://localhost:8092/quit", - "output": [ - { - "name": "http.request", - "resource": "GET /", - "service": "dd-span", - "type": "web", - "meta": { - "component": "net/http", - "http.host": "localhost:8092", - "http.method": "GET", - "http.status_code": "200", - "http.url": "http://localhost:8092/", - "language": "go", - "span.kind": "server" - }, - "_children": [ - { - "name": "spanFromHttpRequest", - "resource": "spanFromHttpRequest", - "service": "dd-span", - "meta": { - "foo": "bar", - "function-name": "spanFromHttpRequest", - "language": "go" - }, - "_children": [ - { - "name": "tagSpecificSpan", - "resource": "tagSpecificSpan", - "service": "dd-span", - "meta": { - "function-name": "tagSpecificSpan", - "language": "go", - "variant": "notag" - } - } - ] - } - ] - } - ], - "variants": { - "tag": { - "flags": [ - "-tags=buildtag" - ], - "output": [ - { - "name": "http.request", - "resource": "GET /", - "service": "dd-span", - "type": "web", - "meta": { - "component": "net/http", - "http.host": "localhost:8092", - "http.method": "GET", - "http.status_code": "200", - "http.url": "http://localhost:8092/", - "language": "go", - "span.kind": "server" - }, - "_children": [ - { - "name": "spanFromHttpRequest", - "resource": "spanFromHttpRequest", - "service": "dd-span", - "meta": { - "foo": "bar", - "function-name": "spanFromHttpRequest", - "language": "go" - }, - "_children": [ - { - "name": "tagSpecificSpan", - "resource": "tagSpecificSpan", - "service": "dd-span", - "meta": { - "function-name": "tagSpecificSpan", - "language": "go", - "variant": "tag" - } - } - ] - } - ] - } - ] - } - } -} diff --git a/_integration-tests/tests/echo.v4/main.go b/_integration-tests/tests/echo.v4/main.go index 4b7360e4..fc6eb60b 100644 --- a/_integration-tests/tests/echo.v4/main.go +++ b/_integration-tests/tests/echo.v4/main.go @@ -3,36 +3,71 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-present Datadog, Inc. -package main +package echo import ( "context" - "log" + "io" "net/http" - "orchestrion/integration" + "orchestrion/integration/validator/trace" + "testing" "time" "github.com/labstack/echo/v4" + "github.com/stretchr/testify/require" ) -func main() { - r := echo.New() - r.GET("/ping", func(c echo.Context) error { - return c.JSON(http.StatusOK, map[string]any{ - "message": "pong", - }) - }) - r.GET("/quit", func(c echo.Context) error { - log.Println("Shutdown requested...") - defer r.Shutdown(context.Background()) - return c.JSON(http.StatusOK, map[string]any{ - "message": "Goodbye", - }) - }) - integration.OnSignal(func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - r.Shutdown(ctx) +type TestCase struct { + *echo.Echo +} + +func (tc *TestCase) Setup(t *testing.T) { + tc.Echo = echo.New() + tc.Echo.Logger.SetOutput(io.Discard) + + tc.Echo.GET("/ping", func(c echo.Context) error { + return c.JSON(http.StatusOK, map[string]any{"message": "pong"}) }) - log.Print(r.Start("127.0.0.1:8081")) + + go func() { require.ErrorIs(t, tc.Echo.Start("127.0.0.1:8080"), http.ErrServerClosed) }() +} + +func (tc *TestCase) Run(t *testing.T) { + resp, err := http.Get("http://127.0.0.1:8080/ping") + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) +} + +func (tc *TestCase) Teardown(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + require.NoError(t, tc.Echo.Shutdown(ctx)) +} + +func (*TestCase) ExpectedTraces() trace.Spans { + return trace.Spans{ + { + // NB: Top-level span is from the HTTP Client, which is library-side instrumented. + Tags: map[string]any{ + "name": "http.request", + "resource": "GET /ping", + "service": "tests.test", + "type": "http", + }, + Children: trace.Spans{ + { + Tags: map[string]any{ + "name": "http.request", + "service": "echo", + "resource": "GET /ping", + "type": "web", + }, + Meta: map[string]any{ + "http.url": "http://127.0.0.1:8080/ping", + }, + }, + }, + }, + } } diff --git a/_integration-tests/tests/echo.v4/validation.json b/_integration-tests/tests/echo.v4/validation.json deleted file mode 100644 index c1b4d1bf..00000000 --- a/_integration-tests/tests/echo.v4/validation.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "url": "http://localhost:8081/ping", - "quit": "http://localhost:8081/quit", - "output": [ - { - "name": "http.request", - "service": "echo", - "resource": "GET /ping", - "type": "web", - "meta": { - "http.url": "http://localhost:8081/ping" - } - } - ] -} diff --git a/_integration-tests/tests/fiber.v2/main.go b/_integration-tests/tests/fiber.v2/main.go index 1a52d458..d2d4bc65 100644 --- a/_integration-tests/tests/fiber.v2/main.go +++ b/_integration-tests/tests/fiber.v2/main.go @@ -3,31 +3,64 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-present Datadog, Inc. -package main +package fiber import ( - "log" - "orchestrion/integration" + "net/http" + "orchestrion/integration/validator/trace" + "testing" + "time" "github.com/gofiber/fiber/v2" + "github.com/stretchr/testify/require" ) -func main() { - r := fiber.New() - r.Get("/ping", func(c *fiber.Ctx) error { - return c.JSON(map[string]any{ - "message": "pong", - }) - }) - r.Get("/quit", func(c *fiber.Ctx) error { - log.Println("Shutdown requested...") - defer r.Shutdown() - return c.JSON(map[string]any{ - "message": "Goodbye", - }) - }) - integration.OnSignal(func() { - r.Shutdown() - }) - log.Print(r.Listen("127.0.0.1:8089")) +type TestCase struct { + *fiber.App +} + +func (tc *TestCase) Setup(t *testing.T) { + tc.App = fiber.New(fiber.Config{DisableStartupMessage: true}) + tc.App.Get("/ping", func(c *fiber.Ctx) error { return c.JSON(map[string]any{"message": "pong"}) }) + go func() { require.NoError(t, tc.App.Listen("127.0.0.1:8080")) }() +} + +func (tc *TestCase) Run(t *testing.T) { + resp, err := http.Get("http://127.0.0.1:8080/ping") + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) +} + +func (tc *TestCase) Teardown(t *testing.T) { + require.NoError(t, tc.App.ShutdownWithTimeout(time.Second)) +} + +func (*TestCase) ExpectedTraces() trace.Spans { + return trace.Spans{ + { + // NB: Top-level span is from the HTTP Client, which is library-side instrumented. + Tags: map[string]any{ + "name": "http.request", + "resource": "GET /ping", + "service": "tests.test", + "type": "http", + }, + Meta: map[string]any{ + "http.url": "http://127.0.0.1:8080/ping", + }, + Children: trace.Spans{ + { + Tags: map[string]any{ + "name": "http.request", + "service": "fiber", + "resource": "GET /ping", + "type": "web", + }, + Meta: map[string]any{ + "http.url": "/ping", + }, + }, + }, + }, + } } diff --git a/_integration-tests/tests/fiber.v2/validation.json b/_integration-tests/tests/fiber.v2/validation.json deleted file mode 100644 index 10e8636e..00000000 --- a/_integration-tests/tests/fiber.v2/validation.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "url": "http://localhost:8089/ping", - "quit": "http://localhost:8089/quit", - "output": [ - { - "name": "http.request", - "service": "fiber", - "resource": "GET /ping", - "type": "web", - "meta": { - "http.url": "/ping" - } - } - ] -} diff --git a/_integration-tests/tests/gin/main.go b/_integration-tests/tests/gin/main.go index 103152ef..67a6cd63 100644 --- a/_integration-tests/tests/gin/main.go +++ b/_integration-tests/tests/gin/main.go @@ -3,44 +3,76 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-present Datadog, Inc. -package main +package gin import ( "context" - "log" "net/http" + "orchestrion/integration/validator/trace" + "testing" "time" - "orchestrion/integration" - "github.com/gin-gonic/gin" + "github.com/stretchr/testify/require" ) -func main() { - r := gin.Default() - //dd:ignore - s := &http.Server{ - Addr: "127.0.0.1:8082", - Handler: r.Handler(), +type TestCase struct { + *http.Server +} + +func (tc *TestCase) Setup(t *testing.T) { + gin.SetMode(gin.ReleaseMode) // Silence start-up logging + engine := gin.New() + + tc.Server = &http.Server{ + Addr: "127.0.0.1:8080", + Handler: engine.Handler(), } - r.GET("/ping", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "pong", - }) - }) - r.GET("/quit", func(c *gin.Context) { - log.Println("Shutdown requested...") - defer s.Shutdown(context.Background()) - c.JSON(http.StatusOK, gin.H{ - "message": "Goodbye", - }) - }) - - integration.OnSignal(func() { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - s.Shutdown(ctx) - }) - log.Print(s.ListenAndServe()) + engine.GET("/ping", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "pong"}) }) + + go func() { require.ErrorIs(t, tc.Server.ListenAndServe(), http.ErrServerClosed) }() +} + +func (tc *TestCase) Run(t *testing.T) { + resp, err := http.Get("http://127.0.0.1:8080/ping") + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) +} + +func (tc *TestCase) Teardown(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + require.NoError(t, tc.Server.Shutdown(ctx)) +} + +func (*TestCase) ExpectedTraces() trace.Spans { + return trace.Spans{ + { + // NB: Top-level span is from the HTTP Client, which is library-side instrumented. + Tags: map[string]any{ + "name": "http.request", + "resource": "GET /ping", + "service": "tests.test", + "type": "http", + }, + Meta: map[string]any{ + "http.url": "http://127.0.0.1:8080/ping", + }, + Children: trace.Spans{ + { + Tags: map[string]any{ + "name": "http.request", + "service": "tests.test", + "resource": "GET /ping", + "type": "web", + }, + Meta: map[string]any{ + "http.url": "http://127.0.0.1:8080/ping", + }, + }, + }, + }, + } } diff --git a/_integration-tests/tests/gin/validation.json b/_integration-tests/tests/gin/validation.json deleted file mode 100644 index 43e6f975..00000000 --- a/_integration-tests/tests/gin/validation.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "url": "http://localhost:8082/ping", - "quit": "http://localhost:8082/quit", - "output": [ - { - "name": "http.request", - "service": "gin.router", - "resource": "GET /ping", - "type": "web", - "meta": { - "http.url": "http://localhost:8082/ping" - } - } - ] -} diff --git a/_integration-tests/tests/go-redis.v7/main.go b/_integration-tests/tests/go-redis.v7/main.go index 4a8f0306..26650010 100644 --- a/_integration-tests/tests/go-redis.v7/main.go +++ b/_integration-tests/tests/go-redis.v7/main.go @@ -3,43 +3,39 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-present Datadog, Inc. -package main +package goredis import ( "context" - "fmt" "log" - "net/http" "net/url" - "orchestrion/integration" - "os" - "runtime" + "orchestrion/integration/validator/trace" + "testing" "time" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" testredis "github.com/testcontainers/testcontainers-go/modules/redis" "github.com/testcontainers/testcontainers-go/wait" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "github.com/go-redis/redis/v7" ) -func main() { - if os.Getenv("DOCKER_NOT_AVAILABLE") != "" { - log.Println("Docker is required to run this test. Exiting with status code 42!") - os.Exit(42) - } +type TestCase struct { + server *testredis.RedisContainer + *redis.Client +} +func (tc *TestCase) Setup(t *testing.T) { ctx := context.Background() - server, err := testredis.RunContainer(ctx, + + var err error + tc.server, err = testredis.RunContainer(ctx, + testcontainers.WithLogger(testcontainers.TestLogger(t)), testcontainers.WithImage("redis:7"), - testcontainers.WithLogConsumers(&testcontainers.StdoutLogConsumer{}), - testcontainers.WithHostConfigModifier(func(config *container.HostConfig) { - if runtime.GOOS == "windows" { - config.NetworkMode = network.NetworkNat - } - }), + testcontainers.WithLogConsumers(testLogConsumer{t}), testcontainers.WithWaitStrategy( wait.ForAll( wait.ForLog("* Ready to accept connections"), @@ -49,11 +45,10 @@ func main() { ), ) if err != nil { - log.Fatalf("Failed to start redis test container: %v\n", err) + t.Skipf("Failed to start redis test container: %v\n", err) } - defer server.Terminate(ctx) - redisURI, err := server.ConnectionString(ctx) + redisURI, err := tc.server.ConnectionString(ctx) if err != nil { log.Fatalf("Failed to obtain connection string: %v\n", err) } @@ -62,44 +57,75 @@ func main() { log.Fatalf("Invalid redis connection string: %q\n", redisURI) } addr := redisURL.Host - client := redis.NewClient(&redis.Options{Addr: addr}) - defer client.Close() + tc.Client = redis.NewClient(&redis.Options{Addr: addr}) +} - if err := client.Set("test_key", "test_value", 0).Err(); err != nil { - log.Fatalf("Failed to insert test data: %v", err) - } +func (tc *TestCase) Run(t *testing.T) { + span, ctx := tracer.StartSpanFromContext(context.Background(), "test.root") + defer span.Finish() - mux := &http.ServeMux{} - s := &http.Server{ - Addr: "127.0.0.1:8090", - Handler: mux, - } + require.NoError(t, tc.Client.WithContext(ctx).Set("test_key", "test_value", 0).Err()) + require.NoError(t, tc.Client.WithContext(ctx).Get("test_key").Err()) +} + +func (tc *TestCase) Teardown(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() - mux.HandleFunc("/quit", - //dd:ignore - func(w http.ResponseWriter, r *http.Request) { - log.Println("Shutdown requested...") - defer s.Shutdown(context.Background()) - w.Write([]byte("Goodbye\n")) - }) + assert.NoError(t, tc.Client.Close()) + assert.NoError(t, tc.server.Terminate(ctx)) +} - mux.HandleFunc("/", - //dd:ignore - func(w http.ResponseWriter, r *http.Request) { - if res, err := client.WithContext(r.Context()).Get("test_key").Result(); err != nil { - log.Printf("Error: %v\n", err) - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "%v\n", err) - } else { - w.Write([]byte(res)) - } - }) +func (*TestCase) ExpectedTraces() trace.Spans { + return trace.Spans{ + { + Tags: map[string]any{ + "name": "test.root", + }, + Children: trace.Spans{ + { + Tags: map[string]any{ + "name": "redis.command", + "service": "redis.client", + "resource": "set", + "type": "redis", + }, + Meta: map[string]any{ + "redis.args_length": "3", + "component": "go-redis/redis.v7", + "out.db": "0", + "span.kind": "client", + "db.system": "redis", + "redis.raw_command": "set test_key test_value: ", + "out.host": "localhost", + }, + }, + { + Tags: map[string]any{ + "name": "redis.command", + "service": "redis.client", + "resource": "get", + "type": "redis", + }, + Meta: map[string]any{ + "redis.args_length": "2", + "component": "go-redis/redis.v7", + "out.db": "0", + "span.kind": "client", + "db.system": "redis", + "redis.raw_command": "get test_key: ", + "out.host": "localhost", + }, + }, + }, + }, + } +} - integration.OnSignal(func() { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - s.Shutdown(ctx) - }) +type testLogConsumer struct { + *testing.T +} - log.Print(s.ListenAndServe()) +func (t testLogConsumer) Accept(log testcontainers.Log) { + t.T.Log(string(log.Content)) } diff --git a/_integration-tests/tests/go-redis.v7/validation.json b/_integration-tests/tests/go-redis.v7/validation.json deleted file mode 100644 index 8417ed65..00000000 --- a/_integration-tests/tests/go-redis.v7/validation.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "url": "http://127.0.0.1:8090", - "quit": "http://127.0.0.1:8090/quit", - "output": [ - { - "name": "http.request", - "service": "go-redis.v7", - "resource": "GET /", - "type": "web", - "meta": { - "component": "net/http", - "http.url": "http://127.0.0.1:8090/" - }, - "_children": [ - { - "name": "redis.command", - "service": "redis.client", - "resource": "get", - "type": "redis", - "meta": { - "redis.args_length": "2", - "component": "go-redis/redis.v7", - "out.db": "0", - "span.kind": "client", - "db.system": "redis", - "redis.raw_command": "get test_key: ", - "out.host": "localhost" - } - } - ] - } - ] -} diff --git a/_integration-tests/tests/go-redis.v8/main.go b/_integration-tests/tests/go-redis.v8/main.go index 98c2eb74..e6b722ff 100644 --- a/_integration-tests/tests/go-redis.v8/main.go +++ b/_integration-tests/tests/go-redis.v8/main.go @@ -3,35 +3,39 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-present Datadog, Inc. -package main +package goredis import ( "context" - "fmt" "log" - "net/http" "net/url" - "orchestrion/integration" - "os" + "orchestrion/integration/validator/trace" + "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" testredis "github.com/testcontainers/testcontainers-go/modules/redis" "github.com/testcontainers/testcontainers-go/wait" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "github.com/go-redis/redis/v8" ) -func main() { - if os.Getenv("DOCKER_NOT_AVAILABLE") != "" { - log.Println("Docker is required to run this test. Exiting with status code 42!") - os.Exit(42) - } +type TestCase struct { + server *testredis.RedisContainer + *redis.Client +} +func (tc *TestCase) Setup(t *testing.T) { ctx := context.Background() - server, err := testredis.RunContainer(ctx, + + var err error + tc.server, err = testredis.RunContainer(ctx, + testcontainers.WithLogger(testcontainers.TestLogger(t)), testcontainers.WithImage("redis:7"), - testcontainers.WithLogConsumers(&testcontainers.StdoutLogConsumer{}), + testcontainers.WithLogConsumers(testLogConsumer{t}), testcontainers.WithWaitStrategy( wait.ForAll( wait.ForLog("* Ready to accept connections"), @@ -41,11 +45,10 @@ func main() { ), ) if err != nil { - log.Fatalf("Failed to start redis test container: %v\n", err) + t.Skipf("Failed to start redis test container: %v\n", err) } - defer server.Terminate(ctx) - redisURI, err := server.ConnectionString(ctx) + redisURI, err := tc.server.ConnectionString(ctx) if err != nil { log.Fatalf("Failed to obtain connection string: %v\n", err) } @@ -54,44 +57,75 @@ func main() { log.Fatalf("Invalid redis connection string: %q\n", redisURI) } addr := redisURL.Host - client := redis.NewClient(&redis.Options{Addr: addr}) - defer client.Close() + tc.Client = redis.NewClient(&redis.Options{Addr: addr}) +} - if err := client.Set(ctx, "test_key", "test_value", 0).Err(); err != nil { - log.Fatalf("Failed to insert test data: %v", err) - } +func (tc *TestCase) Run(t *testing.T) { + span, ctx := tracer.StartSpanFromContext(context.Background(), "test.root") + defer span.Finish() - mux := &http.ServeMux{} - s := &http.Server{ - Addr: "127.0.0.1:8091", - Handler: mux, - } + require.NoError(t, tc.Client.Set(ctx, "test_key", "test_value", 0).Err()) + require.NoError(t, tc.Client.Get(ctx, "test_key").Err()) +} + +func (tc *TestCase) Teardown(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() - mux.HandleFunc("/quit", - //dd:ignore - func(w http.ResponseWriter, r *http.Request) { - log.Println("Shutdown requested...") - defer s.Shutdown(ctx) - w.Write([]byte("Goodbye\n")) - }) + assert.NoError(t, tc.Client.Close()) + assert.NoError(t, tc.server.Terminate(ctx)) +} - mux.HandleFunc("/", - //dd:ignore - func(w http.ResponseWriter, r *http.Request) { - if res, err := client.Get(r.Context(), "test_key").Result(); err != nil { - log.Printf("Error: %v\n", err) - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "%v\n", err) - } else { - w.Write([]byte(res)) - } - }) +func (*TestCase) ExpectedTraces() trace.Spans { + return trace.Spans{ + { + Tags: map[string]any{ + "name": "test.root", + }, + Children: trace.Spans{ + { + Tags: map[string]any{ + "name": "redis.command", + "service": "redis.client", + "resource": "set", + "type": "redis", + }, + Meta: map[string]any{ + "redis.args_length": "3", + "component": "go-redis/redis.v8", + "out.db": "0", + "span.kind": "client", + "db.system": "redis", + "redis.raw_command": "set test_key test_value:", + "out.host": "localhost", + }, + }, + { + Tags: map[string]any{ + "name": "redis.command", + "service": "redis.client", + "resource": "get", + "type": "redis", + }, + Meta: map[string]any{ + "redis.args_length": "2", + "component": "go-redis/redis.v8", + "out.db": "0", + "span.kind": "client", + "db.system": "redis", + "redis.raw_command": "get test_key:", + "out.host": "localhost", + }, + }, + }, + }, + } +} - integration.OnSignal(func() { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - s.Shutdown(ctx) - }) +type testLogConsumer struct { + *testing.T +} - log.Print(s.ListenAndServe()) +func (t testLogConsumer) Accept(log testcontainers.Log) { + t.T.Log(string(log.Content)) } diff --git a/_integration-tests/tests/go-redis.v8/validation.json b/_integration-tests/tests/go-redis.v8/validation.json deleted file mode 100644 index eaf4169b..00000000 --- a/_integration-tests/tests/go-redis.v8/validation.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "url": "http://127.0.0.1:8091", - "quit": "http://127.0.0.1:8091/quit", - "output": [ - { - "name": "http.request", - "service": "go-redis.v8", - "resource": "GET /", - "type": "web", - "meta": { - "component": "net/http", - "http.url": "http://127.0.0.1:8091/" - }, - "_children": [ - { - "name": "redis.command", - "service": "redis.client", - "resource": "get", - "type": "redis", - "meta": { - "redis.args_length": "2", - "component": "go-redis/redis.v8", - "out.db": "0", - "span.kind": "client", - "db.system": "redis", - "redis.raw_command": "get test_key:", - "out.host": "localhost" - } - } - ] - } - ] -} diff --git a/_integration-tests/tests/gorm.jinzhu/main.go b/_integration-tests/tests/gorm.jinzhu/main.go index 6e96ff6f..0c120ece 100644 --- a/_integration-tests/tests/gorm.jinzhu/main.go +++ b/_integration-tests/tests/gorm.jinzhu/main.go @@ -3,35 +3,31 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-present Datadog, Inc. -package main +package gorm import ( "context" - "fmt" - "log" - "net/http" - "orchestrion/integration" - "time" + "orchestrion/integration/validator/trace" + "testing" "github.com/jinzhu/gorm" _ "github.com/mattn/go-sqlite3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" gormtrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/jinzhu/gorm" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) -type Note struct { - gorm.Model - UserID int - Content string +type TestCase struct { + *gorm.DB } -func main() { - db, err := gorm.Open("sqlite3", "file::memory:?cache=shared") - if err != nil { - log.Fatalf("Failed to open GORM database: %v", err) - } - defer db.Close() +func (tc *TestCase) Setup(t *testing.T) { + var err error + tc.DB, err = gorm.Open("sqlite3", "file::memory:") + require.NoError(t, err) - db.AutoMigrate(&Note{}) + require.NoError(t, tc.DB.AutoMigrate(&Note{}).Error) for _, note := range []*Note{ {UserID: 1, Content: `Hello, John. This is John. You are leaving a note for yourself. You are welcome and thank you.`}, {UserID: 1, Content: `Hey, remember to mow the lawn.`}, @@ -40,44 +36,48 @@ func main() { {UserID: 3, Content: `Pick up cabbage from the store on the way home.`}, {UserID: 3, Content: `Review PR #1138`}, } { - db.Create(note) + require.NoError(t, tc.DB.Create(note).Error) } - mux := &http.ServeMux{} - s := &http.Server{ - Addr: "127.0.0.1:8088", - Handler: mux, - } +} - mux.HandleFunc("/quit", - //dd:ignore - func(w http.ResponseWriter, r *http.Request) { - log.Println("Shutdown requested...") - defer s.Shutdown(context.Background()) - w.Write([]byte("Goodbye\n")) - }) +func (tc *TestCase) Run(t *testing.T) { + span, ctx := tracer.StartSpanFromContext(context.Background(), "test.root") + defer span.Finish() - mux.HandleFunc("/", - //dd:ignore - func(w http.ResponseWriter, r *http.Request) { - // TODO: This should not be necessary (it's manual, and manual is yuck) - db := gormtrace.WithContext(r.Context(), db) + var note Note + require.NoError(t, gormtrace.WithContext(ctx, tc.DB).Where("user_id = ?", 2).First(¬e).Error) +} - var note Note - if err := db.Where("user_id = ?", 2).First(¬e).Error; err != nil { - log.Printf("Error: %v\n", err) - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "%v\n", err) - return - } - w.Write([]byte(note.Content)) - }) +func (tc *TestCase) Teardown(t *testing.T) { + assert.NoError(t, tc.DB.Close()) +} - integration.OnSignal(func() { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - s.Shutdown(ctx) - }) +func (*TestCase) ExpectedTraces() trace.Spans { + return trace.Spans{ + { + Tags: map[string]any{ + "name": "test.root", + }, + Children: trace.Spans{ + { + Tags: map[string]any{ + "resource": "SELECT * FROM \"notes\" WHERE \"notes\".\"deleted_at\" IS NULL AND ((user_id = ?)) ORDER BY \"notes\".\"id\" ASC LIMIT 1", + "type": "sql", + "name": "gorm.query", + "service": "gorm.db", + }, + Meta: map[string]any{ + "component": "jinzhu/gorm", + }, + }, + }, + }, + } +} - log.Print(s.ListenAndServe()) +type Note struct { + gorm.Model + UserID int + Content string } diff --git a/_integration-tests/tests/gorm.jinzhu/validation.json b/_integration-tests/tests/gorm.jinzhu/validation.json deleted file mode 100644 index 3924aa09..00000000 --- a/_integration-tests/tests/gorm.jinzhu/validation.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "url": "http://localhost:8088", - "quit": "http://localhost:8088/quit", - "output": [ - { - "resource": "GET /", - "type": "web", - "name": "http.request", - "service": "gorm.jinzhu", - "meta": { - "http.url": "http://localhost:8088/" - }, - "_children": [ - { - "resource": "SELECT * FROM \"notes\" WHERE \"notes\".\"deleted_at\" IS NULL AND ((user_id = ?)) ORDER BY \"notes\".\"id\" ASC LIMIT 1", - "type": "sql", - "name": "gorm.query", - "service": "gorm.db", - "meta": { - "component": "jinzhu/gorm" - } - } - ] - } - ] -} diff --git a/_integration-tests/tests/gorm/main.go b/_integration-tests/tests/gorm/main.go index adb290f0..c473efa0 100644 --- a/_integration-tests/tests/gorm/main.go +++ b/_integration-tests/tests/gorm/main.go @@ -3,90 +3,76 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-present Datadog, Inc. -package main +package gorm import ( "context" - "fmt" - "log" - "net/http" - "orchestrion/integration" - "time" + "orchestrion/integration/validator/trace" + "testing" _ "github.com/mattn/go-sqlite3" + "github.com/stretchr/testify/require" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gorm.io/driver/sqlite" "gorm.io/gorm" ) -type Note struct { - gorm.Model - UserID int - Content string -} - -func main() { - db, err := setup(context.Background()) - if err != nil { - log.Fatalf("Failed to open GORM database: %v", err) - } - - mux := &http.ServeMux{} - s := &http.Server{ - Addr: "127.0.0.1:8088", - Handler: mux, - } - - mux.HandleFunc("/quit", - //dd:ignore - func(w http.ResponseWriter, r *http.Request) { - log.Println("Shutdown requested...") - defer s.Shutdown(context.Background()) - w.Write([]byte("Goodbye\n")) - }) - - mux.HandleFunc("/", - //dd:ignore - func(w http.ResponseWriter, r *http.Request) { - var note Note - if err := db.WithContext(r.Context()).Where("user_id = ?", 2).First(¬e).Error; err != nil { - log.Printf("Error: %v\n", err) - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "%v\n", err) - return - } - w.Write([]byte(note.Content)) - }) - - integration.OnSignal(func() { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - s.Shutdown(ctx) - }) - - log.Print(s.ListenAndServe()) +type TestCase struct { + *gorm.DB } -//dd:span -func setup(ctx context.Context) (*gorm.DB, error) { - db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{}) - if err != nil { - return nil, err - } +func (tc *TestCase) Setup(t *testing.T) { + var err error + tc.DB, err = gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{}) + require.NoError(t, err) - if err := db.WithContext(ctx).AutoMigrate(&Note{}); err != nil { - return nil, err - } + require.NoError(t, tc.DB.AutoMigrate(&Note{})) - if err := db.WithContext(ctx).CreateInBatches([]Note{ + require.NoError(t, tc.DB.CreateInBatches([]Note{ {UserID: 1, Content: `Hello, John. This is John. You are leaving a note for yourself. You are welcome and thank you.`}, {UserID: 1, Content: `Hey, remember to mow the lawn.`}, {UserID: 2, Content: `Reminder to submit that report by Thursday.`}, {UserID: 2, Content: `Opportunities don't happen, you create them.`}, {UserID: 3, Content: `Pick up cabbage from the store on the way home.`}, {UserID: 3, Content: `Review PR #1138`}, - }, 10).Error; err != nil { - return nil, err + }, 10).Error) +} + +func (tc *TestCase) Run(t *testing.T) { + span, ctx := tracer.StartSpanFromContext(context.Background(), "test.root") + defer span.Finish() + + var note Note + require.NoError(t, tc.DB.WithContext(ctx).Where("user_id = ?", 2).First(¬e).Error) +} + +func (tc *TestCase) Teardown(t *testing.T) {} + +func (*TestCase) ExpectedTraces() trace.Spans { + return trace.Spans{ + { + Tags: map[string]any{ + "name": "test.root", + }, + Children: trace.Spans{ + { + Tags: map[string]any{ + "resource": "SELECT * FROM `notes` WHERE user_id = ? AND `notes`.`deleted_at` IS NULL ORDER BY `notes`.`id` LIMIT 1", + "type": "sql", + "name": "gorm.query", + "service": "gorm.db", + }, + Meta: map[string]any{ + "component": "gorm.io/gorm.v1", + }, + }, + }, + }, } +} - return db, nil +type Note struct { + gorm.Model + UserID int + Content string } diff --git a/_integration-tests/tests/gorm/validation.json b/_integration-tests/tests/gorm/validation.json deleted file mode 100644 index d55d7395..00000000 --- a/_integration-tests/tests/gorm/validation.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "url": "http://localhost:8088", - "quit": "http://localhost:8088/quit", - "output": [ - { - "resource": "GET /", - "type": "web", - "name": "http.request", - "service": "gorm", - "_children": [ - { - "resource": "SELECT * FROM `notes` WHERE user_id = ? AND `notes`.`deleted_at` IS NULL ORDER BY `notes`.`id` LIMIT 1", - "type": "sql", - "name": "gorm.query", - "service": "gorm.db", - "meta": { - "component": "gorm.io/gorm.v1" - } - }, - { - "resource": "SELECT * FROM `notes` WHERE user_id = ? AND `notes`.`deleted_at` IS NULL ORDER BY `notes`.`id` LIMIT 1", - "type": "sql", - "name": "sqlite3.query", - "service": "sqlite3.db", - "meta": { - "component": "database/sql", - "span.kind": "client" - } - } - ] - } - ] -} diff --git a/_integration-tests/tests/grpc/client.go b/_integration-tests/tests/grpc/client.go deleted file mode 100644 index c5033a3d..00000000 --- a/_integration-tests/tests/grpc/client.go +++ /dev/null @@ -1,57 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2023-present Datadog, Inc. - -// This file was copied from the grpc-go repository at https://github.com/grpc/grpc-go -// This file is published according to the Apache License Version 2.0 and copyright is -// held by gRPC authors as detailed in the license below: -/* - * - * Copyright 2015 gRPC authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -// Package main implements a client for Greeter service. -package main - -import ( - "context" - "log" - "time" - - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - pb "google.golang.org/grpc/examples/helloworld/helloworld" -) - -func runClient() { - // Set up a connection to the server. - conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials())) - if err != nil { - log.Fatalf("did not connect: %v", err) - } - defer conn.Close() - c := pb.NewGreeterClient(conn) - - // Contact the server and print out its response. - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "rob"}) - if err != nil { - log.Fatalf("could not greet: %v", err) - } - log.Printf("Greeting: %s", r.GetMessage()) -} diff --git a/_integration-tests/tests/grpc/main.go b/_integration-tests/tests/grpc/main.go index ee0ddf33..21d85121 100644 --- a/_integration-tests/tests/grpc/main.go +++ b/_integration-tests/tests/grpc/main.go @@ -3,42 +3,72 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-present Datadog, Inc. -package main +package grpc import ( "context" - "log" - "net/http" + "net" + "orchestrion/integration/validator/trace" + "testing" "time" - "orchestrion/integration" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/examples/helloworld/helloworld" ) -var s *http.Server +type TestCase struct { + *grpc.Server +} -func main() { - go runServer() +func (tc *TestCase) Setup(t *testing.T) { + lis, err := net.Listen("tcp", "127.0.0.1:9090") + require.NoError(t, err) - s = &http.Server{ - Addr: "127.0.0.1:8083", - Handler: http.HandlerFunc(handle), - } - integration.OnSignal(func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - s.Shutdown(ctx) - }) - log.Printf("Server shut down: %v", s.ListenAndServe()) + tc.Server = grpc.NewServer() + helloworld.RegisterGreeterServer(tc.Server, &server{}) + + go func() { require.NoError(t, tc.Server.Serve(lis)) }() } -func handle(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/quit" { - log.Println("Shutdown requested...") - defer s.Shutdown(context.Background()) - w.Write([]byte("Goodbye\n")) - return - } +func (tc *TestCase) Run(t *testing.T) { + conn, err := grpc.NewClient("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials())) + require.NoError(t, err) + defer func() { require.NoError(t, conn.Close()) }() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() - runClient() - w.WriteHeader(http.StatusOK) + client := helloworld.NewGreeterClient(conn) + resp, err := client.SayHello(ctx, &helloworld.HelloRequest{Name: "rob"}) + require.NoError(t, err) + require.Equal(t, "Hello rob", resp.GetMessage()) +} + +func (tc *TestCase) Teardown(t *testing.T) { + tc.Server.GracefulStop() +} + +func (*TestCase) ExpectedTraces() trace.Spans { + return trace.Spans{ + { + Tags: map[string]any{ + "name": "grpc.client", + "service": "grpc.client", + "resource": "/helloworld.Greeter/SayHello", + "type": "rpc", + }, + Children: trace.Spans{ + { + Tags: map[string]any{ + "name": "grpc.server", + "service": "grpc.server", + "resource": "/helloworld.Greeter/SayHello", + "type": "rpc", + }, + }, + }, + }, + } } diff --git a/_integration-tests/tests/grpc/server.go b/_integration-tests/tests/grpc/server.go index 4316a832..c62165a9 100644 --- a/_integration-tests/tests/grpc/server.go +++ b/_integration-tests/tests/grpc/server.go @@ -24,15 +24,12 @@ * */ -// Package main implements a server for Greeter service. -package main +package grpc import ( "context" "log" - "net" - "google.golang.org/grpc" pb "google.golang.org/grpc/examples/helloworld/helloworld" ) @@ -46,16 +43,3 @@ func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloRe log.Printf("Received: %v", in.GetName()) return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil } - -func runServer() { - lis, err := net.Listen("tcp", "127.0.0.1:50051") - if err != nil { - log.Fatalf("failed to listen: %v", err) - } - s := grpc.NewServer() - pb.RegisterGreeterServer(s, &server{}) - log.Printf("server listening at %v", lis.Addr()) - if err := s.Serve(lis); err != nil { - log.Fatalf("failed to serve: %v", err) - } -} diff --git a/_integration-tests/tests/grpc/validation.json b/_integration-tests/tests/grpc/validation.json deleted file mode 100644 index a5e6ba9a..00000000 --- a/_integration-tests/tests/grpc/validation.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "url": "http://localhost:8083", - "quit": "http://localhost:8083/quit", - "output": [ - { - "name": "grpc.client", - "service": "grpc.client", - "resource": "/helloworld.Greeter/SayHello", - "type": "rpc", - "_children": [ - { - "name": "grpc.server", - "service": "grpc.server", - "resource": "/helloworld.Greeter/SayHello", - "type": "rpc" - } - ] - }, - { - "name": "http.request", - "resource": "GET /", - "service": "grpc", - "type": "web" - } - ] -} diff --git a/_integration-tests/tests/mux/main.go b/_integration-tests/tests/mux/main.go index d42e070a..8adfe511 100644 --- a/_integration-tests/tests/mux/main.go +++ b/_integration-tests/tests/mux/main.go @@ -3,47 +3,81 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-present Datadog, Inc. -package main +package mux import ( "context" + "fmt" "io" - "log" "net/http" - "orchestrion/integration" + "orchestrion/integration/validator/trace" + "testing" "time" "github.com/gorilla/mux" + "github.com/stretchr/testify/require" ) -func main() { - r := mux.NewRouter() - ping := func(w http.ResponseWriter, r *http.Request) { +type TestCase struct { + *http.Server +} + +func (tc *TestCase) Setup(t *testing.T) { + mux := mux.NewRouter() + tc.Server = &http.Server{ + Addr: "127.0.0.1:8080", + Handler: mux, + } + + mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) + _, err := io.WriteString(w, `{"message": "pong"}`) + require.NoError(t, err) + }).Methods("GET") - _, _ = io.WriteString(w, `{"message":"pong"}`) - } - r.HandleFunc("/ping", ping).Methods("GET") + go func() { require.ErrorIs(t, tc.Server.ListenAndServe(), http.ErrServerClosed) }() +} - //dd:ignore - s := &http.Server{ - Addr: "127.0.0.1:8088", - Handler: r, - } +func (tc *TestCase) Run(t *testing.T) { + resp, err := http.Get(fmt.Sprintf("http://%s/ping", tc.Server.Addr)) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) +} + +func (tc *TestCase) Teardown(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() - r.HandleFunc("/quit", - //dd:ignore - func(w http.ResponseWriter, r *http.Request) { - log.Println("Shutdown requested...") - defer s.Shutdown(context.Background()) - w.Write([]byte(`{"message":"Goodbye"}\n`)) - }).Methods("GET") - - integration.OnSignal(func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - s.Shutdown(ctx) - }) - log.Print(s.ListenAndServe()) + require.NoError(t, tc.Server.Shutdown(ctx)) +} + +func (tc *TestCase) ExpectedTraces() trace.Spans { + return trace.Spans{ + { + // NB: Top-level span is from the HTTP Client, which is library-side instrumented. + Tags: map[string]any{ + "name": "http.request", + "resource": "GET /ping", + "service": "tests.test", + "type": "http", + }, + Meta: map[string]any{ + "http.url": fmt.Sprintf("http://%s/ping", tc.Server.Addr), + }, + Children: trace.Spans{ + { + Tags: map[string]any{ + "name": "http.request", + "service": "tests.test", + "resource": "GET /ping", + "type": "web", + }, + Meta: map[string]any{ + "http.url": fmt.Sprintf("http://%s/ping", tc.Server.Addr), + }, + }, + }, + }, + } } diff --git a/_integration-tests/tests/mux/validation.json b/_integration-tests/tests/mux/validation.json deleted file mode 100644 index edcf9f95..00000000 --- a/_integration-tests/tests/mux/validation.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "url": "http://localhost:8088/ping", - "quit": "http://localhost:8088/quit", - "output": [ - { - "name": "http.request", - "service": "mux.router", - "resource": "GET /ping", - "type": "web", - "meta": { - "http.url": "http://localhost:8088/ping" - } - } - ] -} diff --git a/_integration-tests/tests/net_http/main.go b/_integration-tests/tests/net_http/main.go index efe96aa9..bf93147c 100644 --- a/_integration-tests/tests/net_http/main.go +++ b/_integration-tests/tests/net_http/main.go @@ -3,79 +3,150 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-present Datadog, Inc. -package main +package nethttp import ( "context" "fmt" "io" - "log" "net/http" - "orchestrion/integration" + "orchestrion/integration/validator/trace" + "testing" "time" -) -var s *http.Server + "github.com/stretchr/testify/require" +) -func main() { - defer log.Printf("Server shutting down gracefully.") +type TestCase struct { + *http.Server +} +func (tc *TestCase) Setup(t *testing.T) { mux := http.NewServeMux() - s = &http.Server{ - Addr: "127.0.0.1:8085", + tc.Server = &http.Server{ + Addr: "127.0.0.1:8080", Handler: mux, } - mux.HandleFunc("/quit", - func(w http.ResponseWriter, r *http.Request) { - log.Println("Shutdown requested...") - defer s.Shutdown(context.Background()) - w.Write([]byte("Goodbye\n")) - return - }) - - mux.HandleFunc("/hit", - func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - - b, err := io.ReadAll(r.Body) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - w.WriteHeader(http.StatusOK) - w.Write(b) - }) - - mux.HandleFunc("/", - func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - - resp, err := http.Post(fmt.Sprintf("http://%s/hit", s.Addr), "text/plain", r.Body) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - defer resp.Body.Close() - - b, err := io.ReadAll(resp.Body) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - return - } - - w.WriteHeader(resp.StatusCode) - w.Write(b) - }) - - integration.OnSignal(func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - s.Shutdown(ctx) - }) - log.Printf("Server shut down: %v", s.ListenAndServe()) + mux.HandleFunc("/hit", tc.handleHit) + mux.HandleFunc("/", tc.handleRoot) + + go func() { require.ErrorIs(t, tc.Server.ListenAndServe(), http.ErrServerClosed) }() +} + +func (tc *TestCase) Run(t *testing.T) { + resp, err := http.Get(fmt.Sprintf("http://%s/", tc.Server.Addr)) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) +} + +func (tc *TestCase) Teardown(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + require.NoError(t, tc.Server.Shutdown(ctx)) +} + +func (tc *TestCase) ExpectedTraces() trace.Spans { + return trace.Spans{ + { + Tags: map[string]any{ + "name": "http.request", + "service": "tests.test", + "resource": "GET /", + "type": "http", + }, + Meta: map[string]any{ + "component": "net/http", + "span.kind": "client", + }, + Children: trace.Spans{ + { + Tags: map[string]any{ + "name": "http.request", + "service": "tests.test", + "resource": "GET /", + "type": "web", + }, + Meta: map[string]any{ + "component": "net/http", + "span.kind": "server", + }, + Children: trace.Spans{ + { + Tags: map[string]any{ + "name": "http.request", + "service": "tests.test", + "resource": "POST /hit", + "type": "http", + }, + Meta: map[string]any{ + "http.url": fmt.Sprintf("http://%s/hit", tc.Server.Addr), + "component": "net/http", + "span.kind": "client", + "network.destination.name": "127.0.0.1", + "http.status_code": "200", + "http.method": "POST", + }, + Children: trace.Spans{ + { + Tags: map[string]any{ + "name": "http.request", + "service": "tests.test", + "resource": "POST /hit", + "type": "web", + }, + Meta: map[string]any{ + "http.useragent": "Go-http-client/1.1", + "http.status_code": "200", + "http.host": tc.Server.Addr, + "component": "net/http", + "http.url": fmt.Sprintf("http://%s/hit", tc.Server.Addr), + "http.method": "POST", + "span.kind": "server", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func (tc *TestCase) handleRoot(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + resp, err := http.Post(fmt.Sprintf("http://%s/hit", tc.Server.Addr), "text/plain", r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + defer resp.Body.Close() + + b, err := io.ReadAll(resp.Body) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.WriteHeader(resp.StatusCode) + w.Write(b) +} + +func (*TestCase) handleHit(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + b, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(b) } diff --git a/_integration-tests/tests/net_http/validation.json b/_integration-tests/tests/net_http/validation.json deleted file mode 100644 index 686c6f73..00000000 --- a/_integration-tests/tests/net_http/validation.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "url": "http://localhost:8085", - "quit": "http://localhost:8085/quit", - "output": [ - { - "name": "http.request", - "service": "net_http", - "resource": "GET /", - "type": "web", - "meta": { - "component": "net/http", - "span.kind": "server" - }, - "_children": [ - { - "name": "http.request", - "service": "net_http", - "resource": "GET /", - "type": "web", - "meta": { - "component": "net/http", - "span.kind": "server" - }, - "_children": [ - { - "name": "http.request", - "service": "net_http", - "resource": "POST /hit", - "type": "http", - "meta": { - "http.url": "http://127.0.0.1:8085/hit", - "component": "net/http", - "span.kind": "client", - "network.destination.name": "127.0.0.1", - "http.status_code": "200", - "http.method": "POST" - }, - "_children": [ - { - "name": "http.request", - "service": "net_http", - "resource": "POST /hit", - "type": "web", - "meta": { - "http.useragent": "Go-http-client/1.1", - "http.status_code": "200", - "http.host": "127.0.0.1:8085", - "component": "net/http", - "http.url": "http://127.0.0.1:8085/hit", - "http.method": "POST", - "span.kind": "server" - } - } - ] - } - ] - } - ] - } - ] -} diff --git a/_integration-tests/tests/net_http_ddspan/main.go b/_integration-tests/tests/net_http_ddspan/main.go deleted file mode 100644 index b058a263..00000000 --- a/_integration-tests/tests/net_http_ddspan/main.go +++ /dev/null @@ -1,59 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2023-present Datadog, Inc. - -package main - -import ( - "context" - "io" - "log" - "net/http" - "orchestrion/integration" - "runtime" - "time" -) - -var s *http.Server - -func main() { - defer log.Printf("Server shutting down gracefully.") - - s = &http.Server{ - Addr: "127.0.0.1:8086", - Handler: http.HandlerFunc(handle), - } - integration.OnSignal(func() { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - s.Shutdown(ctx) - }) - log.Printf("Server shut down: %v", s.ListenAndServe()) -} - -//dd:span test1:subfn -func subfn(ctx context.Context) { - log.Printf("Nothing really to do here.") - runtime.KeepAlive(ctx) -} - -func handle(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/quit" { - log.Println("Shutdown requested...") - defer s.Shutdown(context.Background()) - w.Write([]byte("Goodbye\n")) - return - } - - b, err := io.ReadAll(r.Body) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - defer r.Body.Close() - w.WriteHeader(http.StatusOK) - w.Write(b) - subfn(r.Context()) -} diff --git a/_integration-tests/tests/net_http_ddspan/validation.json b/_integration-tests/tests/net_http_ddspan/validation.json deleted file mode 100644 index a8f80bca..00000000 --- a/_integration-tests/tests/net_http_ddspan/validation.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "url": "http://localhost:8086", - "quit": "http://localhost:8086/quit", - "output": [ - { - "name": "http.request", - "service": "net_http_ddspan", - "resource": "GET /", - "type": "web", - "_children": [ - { - "name": "subfn", - "service": "net_http_ddspan", - "resource": "subfn", - "meta": { - "test1": "subfn", - "function-name": "subfn" - } - } - ] - } - ] -} diff --git a/_integration-tests/tests/redigo/main.go b/_integration-tests/tests/redigo/main.go index 62de23d9..8f0fa531 100644 --- a/_integration-tests/tests/redigo/main.go +++ b/_integration-tests/tests/redigo/main.go @@ -3,42 +3,38 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-present Datadog, Inc. -package main +package redigo import ( "context" - "fmt" "log" - "net/http" "net/url" - "os" - "runtime" + "orchestrion/integration/validator/trace" + "testing" "time" - "orchestrion/integration" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" "github.com/gomodule/redigo/redis" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" testredis "github.com/testcontainers/testcontainers-go/modules/redis" "github.com/testcontainers/testcontainers-go/wait" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) -func main() { - if os.Getenv("DOCKER_NOT_AVAILABLE") != "" { - log.Println("Docker is required to run this test. Exiting with status code 42!") - os.Exit(42) - } +type TestCase struct { + server *testredis.RedisContainer + *redis.Pool +} +func (tc *TestCase) Setup(t *testing.T) { ctx := context.Background() - server, err := testredis.RunContainer(ctx, + + var err error + tc.server, err = testredis.RunContainer(ctx, + testcontainers.WithLogger(testcontainers.TestLogger(t)), testcontainers.WithImage("redis:7"), - testcontainers.WithLogConsumers(&testcontainers.StdoutLogConsumer{}), - testcontainers.WithHostConfigModifier(func(config *container.HostConfig) { - if runtime.GOOS == "windows" { - config.NetworkMode = network.NetworkNat - } - }), + testcontainers.WithLogConsumers(testLogConsumer{t}), testcontainers.WithWaitStrategy( wait.ForAll( wait.ForLog("* Ready to accept connections"), @@ -48,11 +44,9 @@ func main() { ), ) if err != nil { - log.Fatalf("Failed to start redis test container: %v\n", err) + t.Skipf("Failed to start redis test container: %v\n", err) } - defer server.Terminate(ctx) - - redisURI, err := server.ConnectionString(ctx) + redisURI, err := tc.server.ConnectionString(ctx) if err != nil { log.Fatalf("Failed to obtain connection string: %v\n", err) } @@ -61,15 +55,10 @@ func main() { log.Fatalf("Invalid redis connection string: %q\n", redisURI) } - mux := &http.ServeMux{} - s := &http.Server{ - Addr: "127.0.0.1:8089", - Handler: mux, - } - const network = "tcp" address := redisURL.Host - pool := &redis.Pool{ + + tc.Pool = &redis.Pool{ Dial: func() (redis.Conn, error) { return redis.Dial(network, address) }, DialContext: func(ctx context.Context) (redis.Conn, error) { return redis.DialContext(ctx, network, address) }, TestOnBorrow: func(c redis.Conn, _ time.Time) error { @@ -78,49 +67,83 @@ func main() { }, } - func() { - client := pool.Get() - defer client.Close() - - if _, err := client.Do("SET", "test_key", "test_value"); err != nil { - log.Fatalf("Failed to insert test data: %v", err) - } - }() - - mux.HandleFunc("/quit", - //dd:ignore - func(w http.ResponseWriter, r *http.Request) { - log.Println("Shutdown requested...") - defer s.Shutdown(context.Background()) - w.Write([]byte("Goodbye\n")) - }) - - mux.HandleFunc("/", - //dd:ignore - func(w http.ResponseWriter, r *http.Request) { - client, err := pool.GetContext(r.Context()) - if err != nil { - log.Printf("Could not get client: %v", err) - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "%v\n", err) - return - } - defer client.Close() - - if res, err := client.Do("GET", "test_key", r.Context()); err != nil { - log.Printf("Error: %v\n", err) - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "%v\n", err) - } else { - w.Write(res.([]byte)) - } - }) - - integration.OnSignal(func() { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - s.Shutdown(ctx) - }) - - log.Print(s.ListenAndServe()) + client := tc.Pool.Get() + defer func() { require.NoError(t, client.Close()) }() + _, err = client.Do("SET", "test_key", "test_value") + require.NoError(t, err) +} + +func (tc *TestCase) Run(t *testing.T) { + span, ctx := tracer.StartSpanFromContext(context.Background(), "test.root") + defer span.Finish() + + client, err := tc.Pool.GetContext(ctx) + require.NoError(t, err) + defer func() { require.NoError(t, client.Close()) }() + + res, err := client.Do("GET", "test_key", ctx) + require.NoError(t, err) + require.NotEmpty(t, res) +} + +func (tc *TestCase) Teardown(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + assert.NoError(t, tc.Pool.Close()) + assert.NoError(t, tc.server.Terminate(ctx)) +} + +func (tc *TestCase) ExpectedTraces() trace.Spans { + return trace.Spans{ + { + Tags: map[string]any{ + "name": "test.root", + }, + Children: trace.Spans{ + { + Tags: map[string]any{ + "resource": "GET", + "type": "redis", + "name": "redis.command", + "service": "redis.conn", + }, + Meta: map[string]any{ + "redis.raw_command": "GET test_key", + "db.system": "redis", + "component": "gomodule/redigo", + "out.network": "tcp", + "out.host": "localhost", + "redis.args_length": "1", + "span.kind": "client", + }, + }, + }, + }, + { + Tags: map[string]any{ + "resource": "redigo.Conn.Flush", + "type": "redis", + "name": "redis.command", + "service": "redis.conn", + }, + Meta: map[string]any{ + "redis.raw_command": "", + "db.system": "redis", + "component": "gomodule/redigo", + "out.network": "tcp", + "out.host": "localhost", + "redis.args_length": "0", + "span.kind": "client", + }, + }, + } +} + +type testLogConsumer struct { + *testing.T +} + +func (t testLogConsumer) Accept(log testcontainers.Log) { + t.T.Log(string(log.Content)) } diff --git a/_integration-tests/tests/redigo/validation.json b/_integration-tests/tests/redigo/validation.json deleted file mode 100644 index a14a3c8c..00000000 --- a/_integration-tests/tests/redigo/validation.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "url": "http://127.0.0.1:8089", - "quit": "http://127.0.0.1:8089/quit", - "output": [ - { - "resource": "GET /", - "type": "web", - "name": "http.request", - "service": "redigo", - "meta": { - "component": "net/http" - }, - "_children": [ - { - "resource": "GET", - "type": "redis", - "name": "redis.command", - "service": "redis.conn", - "meta": { - "redis.raw_command": "GET test_key", - "db.system": "redis", - "component": "gomodule/redigo", - "out.network": "tcp", - "out.host": "localhost", - "redis.args_length": "1", - "span.kind": "client" - } - } - ] - }, - { - "resource": "redigo.Conn.Flush", - "type": "redis", - "name": "redis.command", - "service": "redis.conn", - "meta": { - "component": "gomodule/redigo", - "out.network": "tcp", - "out.host": "localhost", - "redis.args_length": "0", - "db.system": "redis", - "span.kind": "client" - } - } - ] -} diff --git a/_integration-tests/tests/sql/main.go b/_integration-tests/tests/sql/main.go index 67c7d3d8..32a997c0 100644 --- a/_integration-tests/tests/sql/main.go +++ b/_integration-tests/tests/sql/main.go @@ -3,90 +3,73 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-present Datadog, Inc. -package main +package sql import ( "context" "database/sql" - "fmt" - "log" - "net/http" - "orchestrion/integration" - "os" - "time" + "orchestrion/integration/validator/trace" + "testing" _ "github.com/mattn/go-sqlite3" + "github.com/stretchr/testify/require" ) -func main() { - db, err := sql.Open("sqlite3", "./notedb.sqlite") - if err != nil { - log.Fatalf("Failed to open the notes database: %v", err) - } - defer os.Remove("./notedb.sqlite") +type TestCase struct { + *sql.DB +} - // set up database - _, err = db.ExecContext(context.Background(), +func (tc *TestCase) Setup(t *testing.T) { + var err error + tc.DB, err = sql.Open("sqlite3", "file::memory:") + require.NoError(t, err) + + _, err = tc.DB.ExecContext(context.Background(), `CREATE TABLE IF NOT EXISTS notes ( id INTEGER PRIMARY KEY AUTOINCREMENT, userid INTEGER, content STRING, created STRING )`) - if err != nil { - log.Fatalf("Failed to create table: %v", err) - } + require.NoError(t, err) - _, err = db.ExecContext(context.Background(), - `INSERT OR REPLACE INTO notes(userid, content, created) - VALUES (1, 'Hello, John. This is John. You are leaving a note for yourself. You are welcome and thank you.', datetime('now')), + _, err = tc.DB.ExecContext(context.Background(), + `INSERT OR REPLACE INTO notes(userid, content, created) VALUES + (1, 'Hello, John. This is John. You are leaving a note for yourself. You are welcome and thank you.', datetime('now')), (1, 'Hey, remember to mow the lawn.', datetime('now')), (2, 'Reminder to submit that report by Thursday.', datetime('now')), (2, 'Opportunities don''t happen, you create them.', datetime('now')), (3, 'Pick up cabbage from the store on the way home.', datetime('now')), - (3, 'Review PR #1138', datetime('now')); -`) - if err != nil { - log.Fatalf("Failed to insert test data: %v", err) - } - - mux := &http.ServeMux{} - - //dd:ignore - s := &http.Server{ - Addr: "127.0.0.1:8087", - Handler: mux, - } - - mux.HandleFunc("/new", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "bad request", http.StatusNotFound) - return - } - r.ParseForm() - userid := r.FormValue("userid") - content := r.FormValue("content") - _, err = db.ExecContext(r.Context(), - `INSERT INTO notes (userid, content, created) -VALUES (?, ?, datetime('now'));`, userid, content) - if err != nil { - http.Error(w, fmt.Sprintf("Failed to insert: %v", err), http.StatusInternalServerError) - } - }) + (3, 'Review PR #1138', datetime('now') + );`) + require.NoError(t, err) +} - mux.HandleFunc("/quit", - //dd:ignore - func(w http.ResponseWriter, r *http.Request) { - log.Println("Shutdown requested...") - defer s.Shutdown(context.Background()) - w.Write([]byte("Goodbye\n")) - }) +func (tc *TestCase) Run(t *testing.T) { + _, err := tc.DB.ExecContext(context.Background(), + `INSERT INTO notes (userid, content, created) VALUES (?, ?, datetime('now'));`, + 1337, "This is Elite!") + require.NoError(t, err) +} - integration.OnSignal(func() { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - s.Shutdown(ctx) - }) +func (tc *TestCase) Teardown(t *testing.T) { + require.NoError(t, tc.DB.Close()) +} - log.Print(s.ListenAndServe()) +func (*TestCase) ExpectedTraces() trace.Spans { + return trace.Spans{ + { + Tags: map[string]any{ + "resource": "INSERT INTO notes (userid, content, created) VALUES (?, ?, datetime('now'));", + "type": "sql", + "name": "sqlite3.query", + "service": "sqlite3.db", + }, + Meta: map[string]any{ + "component": "database/sql", + "span.kind": "client", + "sql.query_type": "Exec", + }, + }, + } } diff --git a/_integration-tests/tests/sql/validation.json b/_integration-tests/tests/sql/validation.json deleted file mode 100644 index 4a482676..00000000 --- a/_integration-tests/tests/sql/validation.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "curl": "curl \"http://localhost:8087/new\" -X POST -H \"Content-Type: application/x-www-form-urlencoded\" -d \"userid=1&content=new%20note\"", - "quit": "http://localhost:8087/quit", - "output": [ - { - "resource": "Connect", - "type": "sql", - "name": "sqlite3.query", - "service": "sqlite3.db", - "meta": { - "component": "database/sql", - "span.kind": "client", - "sql.query_type": "Connect" - } - }, - { - "resource": "CREATE TABLE IF NOT EXISTS notes (\n\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\tuserid INTEGER,\n\tcontent STRING,\n\tcreated STRING\n)", - "type": "sql", - "name": "sqlite3.query", - "service": "sqlite3.db", - "meta": { - "component": "database/sql", - "span.kind": "client", - "sql.query_type": "Exec" - } - }, - { - "resource": "INSERT OR REPLACE INTO notes(userid, content, created)\n\tVALUES (1, 'Hello, John. This is John. You are leaving a note for yourself. You are welcome and thank you.', datetime('now')),\n\t\t(1, 'Hey, remember to mow the lawn.', datetime('now')),\n\t\t(2, 'Reminder to submit that report by Thursday.', datetime('now')),\n\t\t(2, 'Opportunities don''t happen, you create them.', datetime('now')),\n\t\t(3, 'Pick up cabbage from the store on the way home.', datetime('now')),\n\t\t(3, 'Review PR #1138', datetime('now'));\n", - "type": "sql", - "name": "sqlite3.query", - "meta": { - "component": "database/sql", - "span.kind": "client", - "sql.query_type": "Exec" - }, - "service": "sqlite3.db" - }, - { - "name": "http.request", - "service": "sql", - "resource": "POST /new", - "meta": { - "http.url": "http://localhost:8087/new" - }, - "_children": [ - { - "resource": "INSERT INTO notes (userid, content, created)\nVALUES (?, ?, datetime('now'));", - "type": "sql", - "name": "sqlite3.query", - "meta": { - "component": "database/sql", - "span.kind": "client", - "sql.query_type": "Exec" - }, - "service": "sqlite3.db" - } - ] - } - ] -} diff --git a/_integration-tests/tests/suite.generated.go b/_integration-tests/tests/suite.generated.go new file mode 100644 index 00000000..d2f854d1 --- /dev/null +++ b/_integration-tests/tests/suite.generated.go @@ -0,0 +1,42 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2023-present Datadog, Inc. +// +// Code generated by 'go generate'; DO NOT EDIT. + +package tests + +import ( + chiv5 "orchestrion/integration/tests/chi.v5" + ddspan "orchestrion/integration/tests/dd-span" + echov4 "orchestrion/integration/tests/echo.v4" + fiberv2 "orchestrion/integration/tests/fiber.v2" + gin "orchestrion/integration/tests/gin" + goredisv7 "orchestrion/integration/tests/go-redis.v7" + goredisv8 "orchestrion/integration/tests/go-redis.v8" + gorm "orchestrion/integration/tests/gorm" + gormjinzhu "orchestrion/integration/tests/gorm.jinzhu" + grpc "orchestrion/integration/tests/grpc" + mux "orchestrion/integration/tests/mux" + nethttp "orchestrion/integration/tests/net_http" + redigo "orchestrion/integration/tests/redigo" + sql "orchestrion/integration/tests/sql" +) + +var suite = map[string]testCase{ + "chi.v5": new(chiv5.TestCase), + "dd-span": new(ddspan.TestCase), + "echo.v4": new(echov4.TestCase), + "fiber.v2": new(fiberv2.TestCase), + "gin": new(gin.TestCase), + "go-redis.v7": new(goredisv7.TestCase), + "go-redis.v8": new(goredisv8.TestCase), + "gorm": new(gorm.TestCase), + "gorm.jinzhu": new(gormjinzhu.TestCase), + "grpc": new(grpc.TestCase), + "mux": new(mux.TestCase), + "net_http": new(nethttp.TestCase), + "redigo": new(redigo.TestCase), + "sql": new(sql.TestCase), +} diff --git a/_integration-tests/tests/suite.go b/_integration-tests/tests/suite.go new file mode 100644 index 00000000..ff1dd3ae --- /dev/null +++ b/_integration-tests/tests/suite.go @@ -0,0 +1,47 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2023-present Datadog, Inc. + +package tests + +import ( + "testing" + + "orchestrion/integration/validator/trace" +) + +//go:generate go run ../utils/generator + +// testCase describes the general contract for tests. Each package in this +// directory is expected to export a `TestCase` structure implementing this +// interface. +type testCase interface { + // Setup is called before the test is run. It should be used to prepare any + // the test for execution, such as starting up services (e.g, databse servers) + // or setting up test data. The Setup function can call `t.SkipNow()` to skip + // the test entirely, for example if prerequisites of its dependencies are not + // satisfied by the test environment. + // + // The tracer is not yet started when Setup is executed. + Setup(*testing.T) + + // Run executes the test case after starting the tracer. This should perform + // the necessary calls to produce trace information from injected + // instrumentation, and assert on expected post-conditions (e.g, HTTP request + // is expected to be successful, database call does not error out, etc...). + // The tracer is shut down after the Run function returns, ensuring + // outstanding spans are flushed to the agent. + Run(*testing.T) + + // Teardown runs if `Setup` was executed successfully and did not call + // `t.SkipNow()`. This can be used to clean up any resources created during + // Setup, such as stopping services or deleting test data. + Teardown(*testing.T) + + // ExpectedTraces returns a trace.Spans object describing all traces expected + // to be produced by the `Run` function. There should be one entry per trace + // root span expected to be produced. Every item in the returned `trace.Spans` + // must match at least one trace received by the agent during the test run. + ExpectedTraces() trace.Spans +} diff --git a/_integration-tests/tests/suite_test.go b/_integration-tests/tests/suite_test.go new file mode 100644 index 00000000..5c7dede0 --- /dev/null +++ b/_integration-tests/tests/suite_test.go @@ -0,0 +1,60 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2023-present Datadog, Inc. + +//go:build integration + +package tests + +import ( + "testing" + + "orchestrion/integration/utils/agent" + "orchestrion/integration/validator/trace" + + "github.com/stretchr/testify/require" +) + +//dd:orchestrion-enabled +const orchestrionEnabled = false + +func Test(t *testing.T) { + require.True(t, orchestrionEnabled, "this test suite must be run with orchestrion enabled") + require.NotEmpty(t, suite, "no test case registered") + + mockAgent, err := agent.New(t) + require.NoError(t, err) + defer mockAgent.Close() + + for name, tc := range suite { + tc := tc + t.Run(name, func(t *testing.T) { + t.Log("Running setup") + tc.Setup(t) + + defer func() { + t.Log("Running teardown") + tc.Teardown(t) + }() + + sess, err := mockAgent.NewSession(t) + require.NoError(t, err) + defer func() { + jsonTraces, err := sess.Close(t) + require.NoError(t, err) + + var traces trace.Spans + require.NoError(t, trace.ParseRaw(jsonTraces, &traces)) + t.Logf("Received %d traces", len(traces)) + + for _, expected := range tc.ExpectedTraces() { + expected.RequireAnyMatch(t, traces) + } + }() + + t.Log("Running test") + tc.Run(t) + }) + } +} diff --git a/_integration-tests/utils/agent/agent.go b/_integration-tests/utils/agent/agent.go new file mode 100644 index 00000000..80470caf --- /dev/null +++ b/_integration-tests/utils/agent/agent.go @@ -0,0 +1,205 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2023-present Datadog, Inc. + +package agent + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + "sync/atomic" + "testing" + "time" + + "github.com/google/uuid" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" +) + +type MockAgent struct { + virtualEnv string + process *exec.Cmd + processCancel context.CancelFunc + currentSession atomic.Pointer[mockSession] + port int +} + +type mockSession struct { + agent *MockAgent + token uuid.UUID +} + +func New(t *testing.T) (*MockAgent, error) { + var ( + agent MockAgent + err error + ) + + ddapmTestAgent, _ := exec.LookPath("ddapm-test-agent") + if ddapmTestAgent == "" { + t.Log("No ddapm-test-agent found in $PATH, installing into a python venv...") + if agent.virtualEnv, err = os.MkdirTemp("", "orchestrion-integ-venv-*"); err != nil { + return nil, err + } + t.Logf("Creating Python venv at %q...\n", agent.virtualEnv) + if err = exec.Command("python3", "-m", "venv", agent.virtualEnv).Run(); err != nil { + return nil, err + } + venvBin := filepath.Join(agent.virtualEnv, "bin") + if runtime.GOOS == "windows" { + venvBin = filepath.Join(agent.virtualEnv, "Scripts") + } + + t.Logf("Installing requirements in venv...\n") + _, thisFile, _, _ := runtime.Caller(0) + thisDir := filepath.Dir(thisFile) + if err = exec.Command(filepath.Join(venvBin, "pip"), "install", "-r", filepath.Join(thisDir, "requirements.txt")).Run(); err != nil { + return nil, err + } + + ddapmTestAgent = filepath.Join(venvBin, "ddapm-test-agent") + } + + if agent.port, err = getFreePort(); err != nil { + return nil, err + } + t.Logf("Starting %s on port %d\n", ddapmTestAgent, agent.port) + var ctx context.Context + ctx, agent.processCancel = context.WithCancel(context.Background()) + agent.process = exec.CommandContext( + ctx, + ddapmTestAgent, + fmt.Sprintf("--port=%d", agent.port), + ) + if err = agent.process.Start(); err != nil { + return nil, err + } + + return &agent, nil +} + +func (a *MockAgent) NewSession(t *testing.T) (session *mockSession, err error) { + token, err := uuid.NewRandom() + if err != nil { + return nil, err + } + session = &mockSession{agent: a, token: token} + if !a.currentSession.CompareAndSwap(nil, session) { + return nil, errors.New("a test session is already in progress") + } + defer func() { + if err != nil { + a.currentSession.Store(nil) + session = nil + } + }() + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://127.0.0.1:%d/test/session/start?test_session_token=%s", a.port, session.token.String()), nil) + if err != nil { + return nil, err + } + + for { + resp, err := http.DefaultClient.Do(req) + if err != nil { + select { + case <-ctx.Done(): + return nil, err + default: + time.Sleep(100 * time.Millisecond) + continue + } + } + if resp.StatusCode != 200 { + return nil, errors.New("test agent returned non-200 status code") + } + break + } + + t.Logf("Started test session with ID %s\n", session.token.String()) + tracer.Start( + tracer.WithAgentAddr(fmt.Sprintf("127.0.0.1:%d", a.port)), + tracer.WithSampler(tracer.NewAllSampler()), + tracer.WithLogStartup(false), + tracer.WithLogger(testLogger{t}), + ) + + return session, nil +} + +type testLogger struct { + *testing.T +} + +func (l testLogger) Log(msg string) { + l.T.Log(msg) +} + +func (a *MockAgent) Close() error { + if !a.currentSession.CompareAndSwap(nil, nil) { + return errors.New("cannot close agent while a test session is in progress") + } + + a.processCancel() + if err := a.process.Wait(); err != nil { + return err + } + + if err := os.RemoveAll(a.virtualEnv); err != nil { + return err + } + + return nil +} + +func (s *mockSession) Port() int { + return s.agent.port +} + +func (s *mockSession) Close(t *testing.T) ([]byte, error) { + if !s.agent.currentSession.CompareAndSwap(s, nil) { + return nil, errors.New("cannot close session that is not the currently active one") + } + + tracer.Flush() + tracer.Stop() + + t.Logf("Closing test session with ID %s\n", s.token.String()) + resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d/test/session/traces?test_session_token=%s", s.agent.port, s.token.String())) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + return data, nil +} + +func getFreePort() (int, error) { + addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") + if err != nil { + return 0, err + } + + listener, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0, err + } + defer listener.Close() + return listener.Addr().(*net.TCPAddr).Port, nil +} diff --git a/_integration-tests/utils/agent/requirements.txt b/_integration-tests/utils/agent/requirements.txt new file mode 100644 index 00000000..c6c293e0 --- /dev/null +++ b/_integration-tests/utils/agent/requirements.txt @@ -0,0 +1 @@ +ddapm-test-agent~=1.17.0 diff --git a/_integration-tests/utils/generator/main.go b/_integration-tests/utils/generator/main.go new file mode 100644 index 00000000..43a960df --- /dev/null +++ b/_integration-tests/utils/generator/main.go @@ -0,0 +1,49 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2023-present Datadog, Inc. + +package main + +import ( + "fmt" + "log" + "os" + "slices" + "strings" + + "github.com/dave/jennifer/jen" +) + +func main() { + file := jen.NewFile("tests") + file.HeaderComment("Unless explicitly stated otherwise all files in this repository are licensed") + file.HeaderComment("under the Apache License Version 2.0.") + file.HeaderComment("This product includes software developed at Datadog (https://www.datadoghq.com/).") + file.HeaderComment("Copyright 2023-present Datadog, Inc.") + file.HeaderComment("") + file.HeaderComment(fmt.Sprintf("Code generated by 'go generate'; DO NOT EDIT.")) + + entries, err := os.ReadDir(".") + if err != nil { + log.Fatalf("failed listing directory: %v\n", err) + } + // Ensure stable ordering by explicitly sorting... + slices.SortFunc(entries, func(lhs, rhs os.DirEntry) int { + return strings.Compare(lhs.Name(), rhs.Name()) + }) + + file.Var().Id("suite").Op("=").Map(jen.String()).Id("testCase").ValuesFunc(func(g *jen.Group) { + for _, entry := range entries { + if !entry.IsDir() { + continue + } + g.Line().Lit(entry.Name()).Op(":").New(jen.Qual(fmt.Sprintf("orchestrion/integration/tests/%s", entry.Name()), "TestCase")) + } + g.Line().Empty() + }) + + if err := file.Save("suite.generated.go"); err != nil { + log.Fatalf("failed writing file: %v\n", err) + } +} diff --git a/_integration-tests/validator/trace/diff.go b/_integration-tests/validator/trace/diff.go index 19000953..c94cc7fd 100644 --- a/_integration-tests/validator/trace/diff.go +++ b/_integration-tests/validator/trace/diff.go @@ -8,17 +8,18 @@ package trace import ( "fmt" "sort" + "testing" + "github.com/stretchr/testify/require" "github.com/xlab/treeprint" ) type Diff treeprint.Tree -// MatchesAny determines whether the receiing span matches any of the other -// spans, and returns difference information if not. -func (span *Span) MatchesAny(others []*Span) (bool, Diff) { +// RequireAnyMatch asserts that any of the traces in `others` corresponds to the receiver. +func (span *Span) RequireAnyMatch(t *testing.T, others []*Span) { span, diff := span.matchesAny(others, treeprint.NewWithRoot("Root")) - return span != nil, diff + require.NotNil(t, span, "no match found for trace:\n%s", diff) } func (span *Span) matchesAny(others []*Span, diff treeprint.Tree) (*Span, Diff) { diff --git a/_integration-tests/validator/trace/formatter/main.go b/_integration-tests/validator/trace/formatter/main.go deleted file mode 100644 index 8c41afbd..00000000 --- a/_integration-tests/validator/trace/formatter/main.go +++ /dev/null @@ -1,72 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2023-present Datadog, Inc. - -package main - -import ( - "encoding/json" - "flag" - "fmt" - "io" - "log" - "orchestrion/integration/validator/trace" - "os" -) - -func main() { - var outputJson bool - flag.BoolVar(&outputJson, "json", false, "Output as JSON") - flag.Parse() - - data, err := io.ReadAll(os.Stdin) - if err != nil { - log.Fatalln(err) - } - - var traces []*trace.Span - if err := trace.ParseRaw(data, &traces); err != nil { - log.Fatalln(err) - } - - if outputJson { - toEncode := make([]map[string]any, len(traces)) - for i, trace := range traces { - toEncode[i] = asMap(trace) - } - - encoder := json.NewEncoder(os.Stdout) - encoder.SetIndent("", " ") - encoder.Encode(toEncode) - } else { - for i, trace := range traces { - if i > 0 { - fmt.Println() - } - fmt.Println(trace) - } - } -} - -func asMap(span *trace.Span) map[string]any { - res := make(map[string]any) - - if span.ID != 0 { - res["span_id"] = span.ID - } - for tag, value := range span.Tags { - res[tag] = value - } - if len(span.Meta) > 0 { - res["meta"] = span.Meta - } - if len(span.Children) > 0 { - children := make([]map[string]any, len(span.Children)) - for i, child := range span.Children { - children[i] = asMap(child) - } - res["_children"] = children - } - return res -} diff --git a/_integration-tests/validator/trace/span.go b/_integration-tests/validator/trace/span.go index 484ec239..ecc5ad88 100644 --- a/_integration-tests/validator/trace/span.go +++ b/_integration-tests/validator/trace/span.go @@ -24,6 +24,8 @@ type Span struct { Children []*Span } +type Spans = []*Span + var _ json.Unmarshaler = &Span{} func (span *Span) UnmarshalJSON(data []byte) error { diff --git a/_integration-tests/validator/trace/span_test.go b/_integration-tests/validator/trace/span_test.go index 446907e9..0ef20917 100644 --- a/_integration-tests/validator/trace/span_test.go +++ b/_integration-tests/validator/trace/span_test.go @@ -16,6 +16,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/xlab/treeprint" "gotest.tools/v3/golden" ) @@ -48,9 +49,9 @@ func TestMatchesAny(t *testing.T) { require.NoError(t, json.Unmarshal(data, &actual)) } - matches, diff := expected.MatchesAny(actual) + matches, diff := expected.matchesAny(actual, treeprint.NewWithRoot("Root")) goldFile := filepath.Join(name, "diff.txt") - if matches { + if matches != nil { golden.Assert(t, "", goldFile) require.Empty(t, diff, 0) } else { diff --git a/_integration-tests/validator/trace/testdata/.gitattributes b/_integration-tests/validator/trace/testdata/.gitattributes index 201360e6..7d16cbc4 100644 --- a/_integration-tests/validator/trace/testdata/.gitattributes +++ b/_integration-tests/validator/trace/testdata/.gitattributes @@ -1 +1 @@ -diff.txt text=auto eol=lf +/*/diff.txt text=auto eol=lf diff --git a/_integration-tests/validator/validator.go b/_integration-tests/validator/validator.go deleted file mode 100644 index 4a066cf3..00000000 --- a/_integration-tests/validator/validator.go +++ /dev/null @@ -1,88 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2023-present Datadog, Inc. - -package main - -import ( - "encoding/json" - "flag" - "fmt" - "log" - "os" - - "orchestrion/integration/validator/trace" -) - -func main() { - var ( - name string - variant string - validationFile string - tracesFile string - ) - - flag.StringVar(&name, "name", "", "The name of the test case") - flag.StringVar(&variant, "variant", "", "The name of the variant to use") - flag.StringVar(&validationFile, "validation", "", "The path to the validation.json file") - flag.StringVar(&tracesFile, "traces", "", "The path to the traces.json file") - flag.Parse() - - if name == "" { - log.Println("Missing -name argument!") - flag.Usage() - os.Exit(2) - } - if validationFile == "" { - log.Println("Missing -validation argument!") - flag.Usage() - os.Exit(2) - } - if tracesFile == "" { - log.Println("Missing -trace argument!") - flag.Usage() - os.Exit(2) - } - - var validation struct { - Traces []*trace.Span `json:"output"` - Variants map[string]struct { - Flags []string `json:"flags"` - Traces []*trace.Span `json:"output"` - } `json:"variants,omitempty"` - } - if data, err := os.ReadFile(validationFile); err != nil { - log.Fatalln("Error reading validation.json file:", err) - } else if err := json.Unmarshal(data, &validation); err != nil { - log.Fatalln("Error parsing contents of validation.json file:", err) - } - - var traces []*trace.Span - if data, err := os.ReadFile(tracesFile); err != nil { - log.Fatalln("Error reading traces.json file:", err) - } else if err := trace.ParseRaw(data, &traces); err != nil { - log.Fatalln("Error parsing traces.json file:", err) - } - - exitCode := 0 - referenceTraces := validation.Traces - if variant != "" { - if setup, found := validation.Variants[variant]; !found { - log.Fatalf("No such variant configured: %q\n", variant) - } else { - referenceTraces = setup.Traces - } - } - for idx, reference := range referenceTraces { - matches, diffs := reference.MatchesAny(traces) - if matches { - fmt.Printf("Successfully matched reference trace %d out of %d\n", idx+1, len(referenceTraces)) - continue - } - exitCode = 1 - fmt.Fprintf(os.Stderr, "Failed to match reference trace %d out of %d:\n%v\nDifferences:\n%s\n", idx+1, len(referenceTraces), reference, diffs) - } - - os.Exit(exitCode) -} diff --git a/docs/layouts/partials/scripts.html b/docs/layouts/partials/scripts.html new file mode 100644 index 00000000..08a19d5c --- /dev/null +++ b/docs/layouts/partials/scripts.html @@ -0,0 +1,75 @@ +{{- $jsTheme := resources.Get "js/theme.js" | resources.ExecuteAsTemplate "theme.js" . -}} +{{- $jsMenu := resources.Get "js/menu.js" -}} +{{- $jsTabs := resources.Get "js/tabs.js" -}} +{{- $jsLang := resources.Get "js/lang.js" -}} +{{- $jsCodeCopy := resources.Get "js/code-copy.js" -}} +{{- $jsFileTree := resources.Get "js/filetree.js" -}} +{{- $jsSidebar := resources.Get "js/sidebar.js" -}} +{{- $jsBackToTop := resources.Get "js/back-to-top.js" -}} + +{{- $scripts := slice $jsTheme $jsMenu $jsCodeCopy $jsTabs $jsLang $jsFileTree $jsSidebar $jsBackToTop | resources.Concat "js/main.js" -}} +{{- if hugo.IsProduction -}} + {{- $scripts = $scripts | minify | fingerprint -}} +{{- end -}} + + + +{{/* Search */}} +{{- if (site.Params.search.enable | default true) -}} + {{- $searchType := site.Params.search.type | default "flexsearch" -}} + {{- if eq $searchType "flexsearch" -}} + {{- $jsSearchScript := printf "%s.search.js" .Language.Lang -}} + {{- $jsSearch := resources.Get "js/flexsearch.js" | resources.ExecuteAsTemplate $jsSearchScript . -}} + {{- if hugo.IsProduction -}} + {{- $jsSearch = $jsSearch | minify | fingerprint -}} + {{- end -}} + {{- $flexSearchJS := resources.Get "lib/flexsearch/flexsearch.bundle.min.js" | fingerprint -}} + + + {{- else -}} + {{- warnf `search type "%s" is not supported` $searchType -}} + {{- end -}} +{{- end -}} + +{{/* Mermaid */}} +{{/* FIXME: need to investigate .Page.Store hasMermaid is set for homepage */}} +{{- if and (.Page.Store.Get "hasMermaid") (not .Page.IsHome) -}} + {{- $mermaidJS := resources.Get "lib/mermaid/mermaid.min.js" | fingerprint -}} + + +{{- end -}} + +{{/* KaTex */}} +{{- if .Page.Params.math -}} + {{- $katexCSS := resources.Get "lib/katex/katex.min.css" | fingerprint -}} + {{- $katexJS := resources.Get "lib/katex/katex.min.js" | fingerprint -}} + {{- $mhchemJS := resources.Get "lib/katex/mhchem.min.js" | fingerprint -}} + {{- $katexAutoRenderJS := resources.Get "lib/katex/auto-render.min.js" | fingerprint -}} + + + + + {{ $katexFonts := resources.Match "lib/katex/fonts/*" }} + {{- range $katexFonts -}} + {{ .Publish }} + {{- end -}} + +{{ end }} diff --git a/go.mod b/go.mod index e1e04153..9497c01a 100644 --- a/go.mod +++ b/go.mod @@ -6,12 +6,12 @@ require ( github.com/charmbracelet/lipgloss v0.11.0 github.com/dave/dst v0.27.3 github.com/dave/jennifer v1.7.0 - github.com/gohugoio/hugo v0.128.1 + github.com/gohugoio/hugo v0.128.2 github.com/google/go-licenses v1.6.0 github.com/shirou/gopsutil/v3 v3.24.5 github.com/stretchr/testify v1.9.0 - golang.org/x/sys v0.21.0 - golang.org/x/term v0.21.0 + golang.org/x/sys v0.22.0 + golang.org/x/term v0.22.0 golang.org/x/tools v0.22.0 gopkg.in/DataDog/dd-trace-go.v1 v1.65.1 gopkg.in/yaml.v3 v3.0.1 @@ -174,7 +174,6 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/hashstructure v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect diff --git a/go.sum b/go.sum index ed5c076e..39b2e084 100644 --- a/go.sum +++ b/go.sum @@ -374,8 +374,8 @@ github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7 github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ= github.com/gohugoio/httpcache v0.7.0 h1:ukPnn04Rgvx48JIinZvZetBfHaWE7I01JR2Q2RrQ3Vs= github.com/gohugoio/httpcache v0.7.0/go.mod h1:fMlPrdY/vVJhAriLZnrF5QpN3BNAcoBClgAyQd+lGFI= -github.com/gohugoio/hugo v0.128.1 h1:lUWyg0nTLUqZPu2lx9rzUhsBimOKRtahOX23NmWdcUA= -github.com/gohugoio/hugo v0.128.1/go.mod h1:Fe6p5/9TZ35+272Mjj0Q5WAOvmKGNWOhaoZhoQgJCCA= +github.com/gohugoio/hugo v0.128.2 h1:VEQ5HqqCG881q1c9VBrt4NyqrSb+1SHMzoX+KzweWe4= +github.com/gohugoio/hugo v0.128.2/go.mod h1:Fe6p5/9TZ35+272Mjj0Q5WAOvmKGNWOhaoZhoQgJCCA= github.com/gohugoio/hugo-goldmark-extensions/extras v0.2.0 h1:MNdY6hYCTQEekY0oAfsxWZU1CDt6iH+tMLgyMJQh/sg= github.com/gohugoio/hugo-goldmark-extensions/extras v0.2.0/go.mod h1:oBdBVuiZ0fv9xd8xflUgt53QxW5jOCb1S+xntcN4SKo= github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.2.0 h1:PCtO5l++psZf48yen2LxQ3JiOXxaRC6v0594NeHvGZg= @@ -626,8 +626,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI= +github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/microsoft/go-mssqldb v0.21.0 h1:p2rpHIL7TlSv1QrbXJUAcbyRKnIT0C9rRkH2E4OjLn8= github.com/microsoft/go-mssqldb v0.21.0/go.mod h1:+4wZTUnz/SV6nffv+RRRB/ss8jPng5Sho2SmM1l2ts4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -1080,14 +1080,14 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/integration-tests.ps1 b/integration-tests.ps1 deleted file mode 100755 index ee8bfd08..00000000 --- a/integration-tests.ps1 +++ /dev/null @@ -1,388 +0,0 @@ -#!/usr/bin/env pwsh - -$integ = Join-Path (Get-Location) "_integration-tests" -$tests = Get-ChildItem -Path (Join-Path $integ "tests") -Name - -if ($Args.Length -gt 0) -{ - $filtered = @() - foreach ($a in $Args) - { - $found = $false - foreach ($t in $tests) - { - if ($t -eq $a) - { - $filtered += $t - $found = $true - break - } - } - if (!$found) - { - Write-Host "Test case '$($a)' does not exit" -ForegroundColor "Red" - } - } - $tests = $filtered -} -if ($tests.Length -eq 0) -{ - Write-Host "No test cases selected, exiting immediately!" -ForegroundColor "Red" - exit 1 -} - -$BinExt = "" -if ($IsWindows) { - $BinExt = ".exe" -} - -$Failed = @{} -$Skipped = @{} -$outputs = Join-Path (Get-Location) "_integration-tests" "outputs" -if (Test-Path $outputs) -{ - Remove-Item -Path $outputs -Recurse -Force -} -$null = New-Item -ItemType Directory -Path $outputs -"*" >(Join-Path $outputs ".gitignore") # So git never considers that content. -"module github.com/datadog/orchestrion/_integration-tests/outputs" >(Join-Path $outputs "go.mod") -"go 1.12" >>(Join-Path $outputs "go.mod") - -# Build orchestrion -Write-Progress -Activity "Preparation" -Status "Building orchestrion" -PercentComplete 0 -$orchestrion = Join-Path $outputs "orchestrion$($BinExt)" -go build -cover -covermode=atomic -coverpkg="github.com/datadog/orchestrion/..." -o $orchestrion . -if ($LastExitCode -ne 0) -{ - throw "Failed to build orchestrion" -} - -$Env:GOCOVERDIR = Join-Path $outputs "coverage" -$null = New-Item -ItemType Directory -Path $Env:GOCOVERDIR -Force - -# Warm up orchestrion -Write-Progress -Activity "Preparation" -Status "Warming up" -PercentComplete 50 -try -{ - $env:ORCHESTRION_LOG_FILE = Join-Path $outputs "warmup" "orchestrion" '$PID.log' - $env:ORCHESTRION_LOG_LEVEL = "TRACE" - $env:GOTMPDIR = Join-Path $outputs "warmup" "tmp" - $null = New-Item -ItemType Directory -Path $env:GOTMPDIR # The directory must exist... - & $orchestrion warmup -work 2>&1 1>(Join-Path $outputs "warmup" "output.log") - if ($LastExitCode -ne 0) - { - throw "Failed to warm up orchestrion" - } -} -finally -{ - $env:GOTMPDIR = $null - $env:ORCHESTRION_LOG_LEVEL = $null - $env:ORCHESTRION_LOG_FILE = $null -} - -Write-Progress -Activity "Preparation" -Status "Install test agent" -PercentComplete 75 -$venv = $(Join-Path $outputs "venv") -python -m venv $venv 2>&1 1> (Join-Path $outputs "python-venv.log") -if ($LastExitCode -ne 0) -{ - throw "Failed to create python virtual environment" -} -Write-Progress -Activity "Preparation" -Status "Install test agent" -PercentComplete 85 - -$scripts = "bin" -if ($IsWindows) -{ - # On Windows, the venv binaries directory is called "Scripts" for some reason. - $scripts = "Scripts" -} -. (Join-Path $venv $scripts "Activate.ps1") -pip install "ddapm-test-agent" 2>&1 1> (Join-Path $outputs "pip.log") -if ($LastExitCode -ne 0) -{ - throw "Failed to pip install ddapm-test-agent" -} - -Write-Progress -Activity "Preparation" -Completed - -# Running test cases -Write-Progress -Activity "Testing" -Status "Initialization" -PercentComplete 0 -try -{ - if ((docker context inspect --format '{{ .Name }}') -eq "colima") - { - $env:DOCKER_HOST = docker context inspect --format '{{ .Endpoints.docker.Host }}' - $env:TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE = '/var/run/docker.sock' - } - elseif ($IsWindows) - { - $osType = docker info --format '{{ .OSType }}' - if ("linux" -ne $osType) - { - throw "Unable to run Linux containers (OS Type of Docker is $($osType))" - } - } -} -catch -{ - Write-Host "Docker is not available ($($_.Exception)), skipping tests that require it" -ForegroundColor "Yellow" - $env:DOCKER_NOT_AVAILABLE = 'true' -} -for ($i = 0 ; $i -lt $tests.Length ; $i++) -{ - $name = $tests[$i] - Write-Progress -Activity "Testing" -Status "$($name) ($($i+1) of $($tests.Length))" -PercentComplete (100 * $i / $tests.Length) - try { - # Parse validator instructions - $vfile = Join-Path $integ "tests" $name "validation.json" - $json = Get-Content -Raw $vfile | ConvertFrom-Json - - # Create complete list of variants (the basic one being blank) - $variants = @{"" = @()} - if ($null -ne $json.variants) - { - foreach ($v in $json.variants.PSObject.Properties) - { - $variants[$v.Name] = $v.Value.flags - } - } - - foreach ($var in $variants.GetEnumerator()) - { - # Build test case - $outDir = Join-Path $outputs "tests" $name - $logName = $name - if ($var.Name -ne "") - { - $outDir = "$($outDir)@$($var.Name)" - $logName = "$($logName)@$($var.Name)" - } - $extraArgs = $var.Value - - $null = New-Item -ItemType Directory -Path $outDir # Ensure the directory exists - - $bin = Join-Path $outDir "$($name)$($BinExt)" - try - { - $env:ORCHESTRION_LOG_FILE = Join-Path $outDir "orchestrion-log" '$PID.log' - $env:ORCHESTRION_LOG_LEVEL = "TRACE" - $env:GOTMPDIR = Join-Path $outDir "tmp" - $oldGoFlags = $env:GOFLAGS - - $null = New-Item -ItemType Directory -Path $env:GOTMPDIR # The directory must exist... - switch ($env:TESTCASE_BUILD_MODE) - { - "TOOLEXEC" - { - Write-Output "[$($logName)]: Building with manual -toolexec command" - go -C $integ build @extraArgs ` - -toolexec "$($orchestrion) toolexec" ` - -work -o $bin "./tests/$($name)" 2>&1 1>(Join-Path $outDir "build.log") - } - "GOFLAGS" - { - Write-Output "[$($logName)]: Building with GOFLAGS command" - $env:GOFLAGS = "$($oldGoFlags) '-toolexec=$($orchestrion) toolexec'" - go -C $integ build @extraArgs -work -o $bin "./tests/$($name)" 2>&1 1>(Join-Path $outDir "build.log") - } - default - { - Write-Output "[$($logName)]: Building with orchestrion driver command (TESTCASE_BUILD_MODE=$($env:TESTCASE_BUILD_MODE))" - & $orchestrion go -C $integ build @extraArgs ` - -work -o $bin "./tests/$($name)" 2>&1 1>(Join-Path $outDir "build.log") - } - } - if ($LastExitCode -ne 0) - { - Write-Output "[$($logName)] Build failed; output follows:" - Get-Content -Path (Join-Path $outDir "build.log") | Write-Output - throw "Failed to build test case" - } - } - finally - { - $env:GOFLAGS = $oldGoFlags - $env:GOTMPDIR = $null - $env:ORCHESTRION_LOG_LEVEL = $null - $env:ORCHESTRION_LOG_FILE = $null - } - - # Run test case - $env:TRACE_LANGUAGE = 'golang' - $env:LOG_LEVEL = 'DEBUG' - $env:ENABLED_CHECKS = 'trace_stall,trace_count_header,trace_peer_service,trace_dd_service' - $agent = (& (Join-Path $venv $scripts "ddapm-test-agent") 2>&1 1>(Join-Path $outDir "agent.log")) & - - $server = Start-Process -FilePath $bin -RedirectStandardOutput (Join-Path $outDir "stdout.log") -RedirectStandardError (Join-Path $outDir "stderr.log") -PassThru - try - { - $token = New-Guid - $attemptsLeft = 10 - for (;;) - { - try - { - if ($agent.State -ne "Running") - { - throw "Agent is no longer running (state: $($agent.State))" - } - $null = Invoke-WebRequest -Uri "http://localhost:8126/test/session/start?test_session_token=$($token)" -MaximumRetryCount 15 -RetryIntervalSec 1 - break # Invoke-WebRequest returns IIF the response had a successful status code - } - catch [System.Net.Http.HttpRequestException] - { - if ($null -ne $_.Exception.Response.StatusCode) - { - throw "Failed to start test session: HTTP $($_.Exception.Response.StatusCode) - $($_.Exception.Response.StatusDescription)" - } - elseif ($attemptsLeft -le 0) - { - throw "Failed to start test session: Failed and all attempts are exhaused. Last error: $($_)" - } - else - { - $attemptsLeft-- - Start-Sleep -Milliseconds 150 - } - } - } - - # Perform validations - $skip = false - if ($null -ne $json.url) - { - Write-Output "[$($logName)]: Validating using: GET $($json.url)" - $attemptsLeft = 600 # 60 seconds with poll interval of 100ms - for (;;) - { - try - { - $null = Invoke-WebRequest -Uri $json.url - break # Invoke-WebRequest returns IIF the response had a successful status code - } - catch [System.Net.Http.HttpRequestException] - { - if ($null -ne $_.Exception.Response.StatusCode) - { - throw "GET $($json.url) => HTTP $([int]$_.Exception.Response.StatusCode) ($($_.Exception.Response.StatusCode))" - } - elseif ($attemptsLeft -le 0) - { - throw "GET $($json.url) => Failed and all attempts are exhaused. Last error: $($_)" - } - elseif ($server.HasExited) - { - if ($server.ExitCode -eq 42) - { - $skip = $true - break - } - throw "GET $($json.url) => Failed and server is no longer running. Last error: $($_)" - } - else - { - $attemptsLeft-- - Start-Sleep -Milliseconds 100 - } - } - } - } - elseif ($null -ne $json.curl) - { - Write-Output "[$($logName)]: Validating using: $($json.curl)" - Invoke-Expression "$($json.curl) --retry 5 --retry-all-errors --retry-max-time 30 2>&1 1>$(Join-Path $outDir "curl.log")" - if ($LastExitCode -ne 0) - { - throw "Validation failed: $($json.curl)" - } - } - else - { - throw "No validation instructions found!" - } - - if ($skip) - { - Write-Host "[$($logName)]: Unsupported on this platform" -ForegroundColor "Yellow" - $Skipped.$name = $true - } - else - { - Write-Output "[$($logName)]: Validation was successful" - try - { - $null = Invoke-WebRequest -Uri $json.quit -MaximumRetryCount 5 -RetryIntervalSec 1 - } - catch - { - $null = $_ # Swallow the exception - } - - $server.WaitForExit() - for (;;) - { - $resp = Invoke-WebRequest -Uri "http://localhost:8126/test/session/traces?test_session_token=$($token)" -MaximumRetryCount 5 -RetryIntervalSec 1 - $data = $resp.Content | ConvertFrom-Json - if ($data.Length -ne 0) - { - Write-Output "[$($logName)]: Collected $($data.Length) traces" - $tracesFile = Join-Path $outDir "traces.json" - $resp.Content > $($tracesFile) - - go -C $integ run ./validator -name $name -variant $var.Name -validation $vfile -traces $tracesFile 2>&1 | Write-Host - if ($LastExitCode -ne 0) - { - throw "Validation of traces failed" - } - - Write-Host "[$($logName)]: Success!" -ForegroundColor "Green" - break - } - } - } - } - finally - { - if (!$server.HasExited) - { - $server.Kill($true) - } - Remove-Job -Job $agent -Force - } - } - } - catch - { - Write-Host "[$($name)]: Failed: $($_)" -ForegroundColor "Red" - $Failed.$name = $_ - } -} -Write-Progress -Activity "Testing" -Completed - -Write-Host "" -Write-Host "###########################" -ForegroundColor "Blue" -Write-Host "Summary:" -ForegroundColor "Blue" -foreach ($t in $tests) -{ - $color = "Green" - $icon = "✅" - $status = "Success" - if ($null -ne $Failed.$t) - { - $color = "Red" - $icon = "💥" - $status = $Failed.$t - } - elseif ($null -ne $Skipped.$t) - { - $color = "Yellow" - $icon = "⚠️" - $status = "Skipped (unsupported on this platform)" - } - Write-Host "- $($icon) $($t): $($status)" -ForegroundColor $color -} - -if ($Failed.Count -gt 0) -{ - exit 1 -} diff --git a/internal/toolexec/aspect/oncompile.go b/internal/toolexec/aspect/oncompile.go index eb771e5a..a9b7e432 100644 --- a/internal/toolexec/aspect/oncompile.go +++ b/internal/toolexec/aspect/oncompile.go @@ -73,10 +73,11 @@ func (w Weaver) OnCompile(cmd *proxy.CompileCommand) error { orchestrionDir := filepath.Join(filepath.Dir(cmd.Flags.Output), "orchestrion") injector, err := injector.New(cmd.SourceDir, injector.Options{ - Aspects: aspects, + Aspects: aspects, + // Include test files if any of the input Go files has a _test.go suffix. IncludeTests: slices.ContainsFunc(cmd.GoFiles(), func(name string) bool { return strings.HasSuffix(strings.ToLower(name), "_test.go") }), ModifiedFile: func(file string) string { - return filepath.Join(orchestrionDir, "src", filepath.Base(file)) + return filepath.Join(orchestrionDir, "src", cmd.Flags.Package, filepath.Base(file)) }, PreserveLineInfo: true, }) diff --git a/internal/version/version.go b/internal/version/version.go index 43d1aca4..5e1079e7 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -6,4 +6,4 @@ package version // Tag specifies the current release tag. It needs to be manually updated. -const Tag = "v0.7.1" +const Tag = "v0.7.2" diff --git a/pin_test.go b/pin_test.go index 785250d5..f267b26f 100644 --- a/pin_test.go +++ b/pin_test.go @@ -38,6 +38,9 @@ func TestPin(t *testing.T) { } func scaffold(dir string) error { + _, thisFile, _, _ := runtime.Caller(0) + thisDir := filepath.Dir(thisFile) + goMod, err := os.Create(filepath.Join(dir, "go.mod")) if err != nil { return err @@ -50,7 +53,13 @@ func scaffold(dir string) error { if _, err := fmt.Fprintln(goMod); err != nil { return err } - if _, err := fmt.Fprintf(goMod, "go %s", runtime.Version()[2:6]); err != nil { + if _, err := fmt.Fprintf(goMod, "go %s\n", runtime.Version()[2:6]); err != nil { + return err + } + if _, err := fmt.Fprintln(goMod); err != nil { + return err + } + if _, err := fmt.Fprintf(goMod, "replace github.com/datadog/orchestrion %s => %s\n", version.Tag, thisDir); err != nil { return err } diff --git a/samples/go.mod b/samples/go.mod index 2c96e4d9..ca30b80a 100644 --- a/samples/go.mod +++ b/samples/go.mod @@ -16,15 +16,14 @@ require ( github.com/jinzhu/gorm v1.9.16 github.com/labstack/echo/v4 v4.12.0 github.com/mattn/go-sqlite3 v1.14.22 - google.golang.org/grpc v1.64.0 + google.golang.org/grpc v1.65.0 gorm.io/driver/postgres v1.5.9 gorm.io/gorm v1.25.10 ) require ( cloud.google.com/go v0.112.1 // indirect - cloud.google.com/go/compute v1.25.1 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/iam v1.1.6 // indirect cloud.google.com/go/storage v1.38.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 // indirect @@ -125,7 +124,7 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e // indirect github.com/gohugoio/httpcache v0.7.0 // indirect - github.com/gohugoio/hugo v0.128.1 // indirect + github.com/gohugoio/hugo v0.128.2 // indirect github.com/gohugoio/hugo-goldmark-extensions/extras v0.2.0 // indirect github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.2.0 // indirect github.com/gohugoio/locales v0.14.0 // indirect @@ -242,19 +241,18 @@ require ( golang.org/x/image v0.16.0 // indirect golang.org/x/mod v0.18.0 // indirect golang.org/x/net v0.26.0 // indirect - golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/oauth2 v0.20.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/term v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/term v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.22.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/api v0.169.0 // indirect - google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/DataDog/dd-trace-go.v1 v1.65.1 // indirect gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect diff --git a/samples/go.sum b/samples/go.sum index d9731743..b4b029b9 100644 --- a/samples/go.sum +++ b/samples/go.sum @@ -46,10 +46,8 @@ cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6m cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= -cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= @@ -374,8 +372,8 @@ github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7 github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ= github.com/gohugoio/httpcache v0.7.0 h1:ukPnn04Rgvx48JIinZvZetBfHaWE7I01JR2Q2RrQ3Vs= github.com/gohugoio/httpcache v0.7.0/go.mod h1:fMlPrdY/vVJhAriLZnrF5QpN3BNAcoBClgAyQd+lGFI= -github.com/gohugoio/hugo v0.128.1 h1:lUWyg0nTLUqZPu2lx9rzUhsBimOKRtahOX23NmWdcUA= -github.com/gohugoio/hugo v0.128.1/go.mod h1:Fe6p5/9TZ35+272Mjj0Q5WAOvmKGNWOhaoZhoQgJCCA= +github.com/gohugoio/hugo v0.128.2 h1:VEQ5HqqCG881q1c9VBrt4NyqrSb+1SHMzoX+KzweWe4= +github.com/gohugoio/hugo v0.128.2/go.mod h1:Fe6p5/9TZ35+272Mjj0Q5WAOvmKGNWOhaoZhoQgJCCA= github.com/gohugoio/hugo-goldmark-extensions/extras v0.2.0 h1:MNdY6hYCTQEekY0oAfsxWZU1CDt6iH+tMLgyMJQh/sg= github.com/gohugoio/hugo-goldmark-extensions/extras v0.2.0/go.mod h1:oBdBVuiZ0fv9xd8xflUgt53QxW5jOCb1S+xntcN4SKo= github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.2.0 h1:PCtO5l++psZf48yen2LxQ3JiOXxaRC6v0594NeHvGZg= @@ -985,8 +983,8 @@ golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1082,14 +1080,14 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1099,7 +1097,6 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= @@ -1226,8 +1223,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1315,10 +1310,10 @@ google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljW google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= -google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= -google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 h1:Q2RxlXqh1cgzzUgV261vBO2jI5R/3DD1J2pM0nI4NhU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1351,8 +1346,8 @@ google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= -google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=