From 42f69b22cc520ee0bf886e88f456dcb1032d0a80 Mon Sep 17 00:00:00 2001 From: Kyle Brennan Date: Thu, 1 Oct 2026 16:27:47 +0000 Subject: [PATCH 1/4] Remove GitHub user credentials from integration test suites Co-authored-by: Codex --- .github/actions/integration-tests/action.yml | 13 +- .github/workflows/branch-build.yml | 2 - .github/workflows/build.yml | 2 - .github/workflows/ide-integration-tests.yml | 10 +- .../preview-env-check-regressions.yml | 8 +- .../workflows/workspace-integration-tests.yml | 12 +- dev/jetbrains-test/README.md | 37 +- test/BUILD.yaml | 32 +- test/README.md | 118 +++-- test/pkg/integration/apis.go | 110 ----- test/pkg/integration/disk-client.go | 2 +- test/pkg/integration/setup.go | 8 +- test/run.sh | 23 +- .../ws-daemon/network_limiting_test.go | 97 +---- .../components/ws-manager/dotfiles_test.go | 185 +------- .../ide/jetbrains/base_in_workspace_test.go | 49 --- test/tests/ide/jetbrains/base_test.go | 412 ------------------ test/tests/ide/jetbrains/gateway_test.go | 267 +----------- test/tests/ide/jetbrains/warmup-indexing.sh | 38 -- test/tests/ide/ssh/ssh_gateway_test.go | 109 +---- test/tests/ide/vscode/python_ws_test.go | 176 +------- test/tests/smoke-test/smoke_test.go | 49 +-- test/tests/workspace/contexts_test.go | 187 +------- test/tests/workspace/disk_test.go | 109 +---- test/tests/workspace/example_test.go | 85 +--- test/tests/workspace/git_hooks_test.go | 123 +----- test/tests/workspace/git_test.go | 200 +-------- test/tests/workspace/ports_test.go | 209 +-------- test/tests/workspace/process_priority_test.go | 136 +----- 29 files changed, 176 insertions(+), 2632 deletions(-) delete mode 100644 test/tests/ide/jetbrains/base_in_workspace_test.go delete mode 100644 test/tests/ide/jetbrains/base_test.go delete mode 100755 test/tests/ide/jetbrains/warmup-indexing.sh diff --git a/.github/actions/integration-tests/action.yml b/.github/actions/integration-tests/action.yml index 2a009a6ff46faf..42cdc9fc9d3823 100644 --- a/.github/actions/integration-tests/action.yml +++ b/.github/actions/integration-tests/action.yml @@ -27,12 +27,6 @@ inputs: test_build_ref: description: "The build ref of the test run. Used in the IDE integration tests." required: false - integration_test_username: - description: "The username for integration test" - required: true - integration_test_usertoken: - description: "The username for integration test" - required: true identity_provider: description: "GCP workload identity provider" required: true @@ -83,8 +77,6 @@ runs: shell: bash env: ROBOQUAT_TOKEN: ${{ inputs.github_token }} - INTEGRATION_TEST_USERNAME: ${{ inputs.integration_test_username }} - INTEGRATION_TEST_USER_TOKEN: ${{ inputs.integration_test_usertoken }} PREVIEW_NAME: ${{ inputs.preview_name }} TEST_USE_LATEST_VERSION: ${{ inputs.latest_ide_version }} TEST_BUILD_ID: ${{ inputs.test_build_id }} @@ -122,6 +114,11 @@ runs: paths: "test/**/TEST-*.xml" show: "all" if: always() + - name: Explain disabled IDE coverage + if: ${{ always() && contains(fromJSON('["ide", "jetbrains", "vscode", "ssh", "all", ""]'), inputs.test_suite) }} + shell: bash + run: | + printf '%s\n' 'IDE integration coverage is disabled because it requires a GitHub user token. Skipped IDE tests do not validate IDE or SSH gateway functionality. See test/README.md for retained coverage.' >> "$GITHUB_STEP_SUMMARY" - name: Slack Notification uses: rtCamp/action-slack-notify@v2 if: ${{ (success() || failure()) && inputs.notify_slack_webhook != '' }} diff --git a/.github/workflows/branch-build.yml b/.github/workflows/branch-build.yml index 29bbb573b6ea88..b09b3b7c80b3ca 100644 --- a/.github/workflows/branch-build.yml +++ b/.github/workflows/branch-build.yml @@ -576,8 +576,6 @@ jobs: identity_provider: ${{ secrets.DEV_PREVIEW_PROVIDER }} service_account: ${{ secrets.DEV_PREVIEW_SA }} leeway_segment_key: ${{ secrets.LEEWAY_SEGMENT_KEY }} - integration_test_username: ${{ secrets.WORKSPACE_INTEGRATION_TEST_USERNAME }} - integration_test_usertoken: ${{ secrets.WORKSPACE_INTEGRATION_TEST_USER_TOKEN }} workspace-integration-tests-main: name: "Run workspace integration tests on main branch" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ceb284534475a1..276991baa5713b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -614,8 +614,6 @@ jobs: identity_provider: ${{ secrets.DEV_PREVIEW_PROVIDER }} service_account: ${{ secrets.DEV_PREVIEW_SA }} leeway_segment_key: ${{ secrets.LEEWAY_SEGMENT_KEY }} - integration_test_username: ${{ secrets.WORKSPACE_INTEGRATION_TEST_USERNAME }} - integration_test_usertoken: ${{ secrets.WORKSPACE_INTEGRATION_TEST_USER_TOKEN }} workspace-integration-tests-main: name: "Run workspace integration tests on main branch" diff --git a/.github/workflows/ide-integration-tests.yml b/.github/workflows/ide-integration-tests.yml index 5c1da72397fc19..29ec026e426f89 100644 --- a/.github/workflows/ide-integration-tests.yml +++ b/.github/workflows/ide-integration-tests.yml @@ -145,8 +145,6 @@ jobs: shell: bash env: ROBOQUAT_TOKEN: ${{ secrets.GITHUB_TOKEN }} - USERNAME: ${{ secrets.IDE_INTEGRATION_TEST_USERNAME }} - USER_TOKEN: ${{ secrets.IDE_INTEGRATION_TEST_USER_TOKEN }} PREVIEW_NAME: ${{ needs.configuration.outputs.name }} TEST_BUILD_ID: ${{ github.run_id }} TEST_BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} @@ -165,7 +163,6 @@ jobs: args=() args+=( "-kubeconfig=$HOME/.kube/config" ) args+=( "-namespace=default" ) - [[ "$USERNAME" != "" ]] && args+=( "-username=$USERNAME" ) args+=( "-timeout=60m" ) IDE_TESTS_DIR="$GITHUB_WORKSPACE/test/tests/ide" @@ -201,13 +198,18 @@ jobs: with: paths: "test/tests/**/TEST-*.xml" if: always() + - name: Explain disabled IDE coverage + if: always() + shell: bash + run: | + printf '%s\n' 'All 13 IDE integration tests are disabled because they require a GitHub user token. This workflow checks deployment/readiness but provides no functional IDE or SSH gateway coverage. Skipped tests are not passing functional checks.' >> "$GITHUB_STEP_SUMMARY" - name: Slack Notification uses: rtCamp/action-slack-notify@cdf0a2130cbcdfd82ba5fcac8e076370bf381b36 # pin@v2 if: success() || failure() env: SLACK_WEBHOOK: ${{ secrets.IDE_SLACK_WEBHOOK }} SLACK_COLOR: ${{ job.status }} - SLACK_MESSAGE: ${{ steps.test_summary.outputs.passed }}/${{ steps.test_summary.outputs.total }} tests passed + SLACK_MESSAGE: "IDE integration coverage disabled (GitHub user token removed). ${{ steps.test_summary.outputs.passed }} passed, ${{ steps.test_summary.outputs.failed }} failed, ${{ steps.test_summary.outputs.skipped }} skipped." SLACK_FOOTER: "" delete: diff --git a/.github/workflows/preview-env-check-regressions.yml b/.github/workflows/preview-env-check-regressions.yml index 824692f0c4c4d9..b8f447459ed51c 100644 --- a/.github/workflows/preview-env-check-regressions.yml +++ b/.github/workflows/preview-env-check-regressions.yml @@ -110,8 +110,6 @@ jobs: shell: bash env: ROBOQUAT_TOKEN: ${{ secrets.GITHUB_TOKEN }} - USERNAME: ${{ secrets.IDE_INTEGRATION_TEST_USERNAME }} - USER_TOKEN: ${{ secrets.IDE_INTEGRATION_TEST_USER_TOKEN }} PREVIEW_NAME: ${{ needs.configuration.outputs.name }} run: | set -euo pipefail @@ -126,7 +124,6 @@ jobs: args=() args+=( "-kubeconfig=/home/gitpod/.kube/config" ) args+=( "-namespace=default" ) - [[ "$USERNAME" != "" ]] && args+=( "-username=$USERNAME" ) args+=( "-timeout=60m" ) TESTS_DIR="$GITHUB_WORKSPACE/test/tests/smoke-test" @@ -150,6 +147,11 @@ jobs: with: paths: "test/tests/**/TEST.xml" if: always() + - name: Explain disabled workspace smoke coverage + if: always() + shell: bash + run: | + printf '%s\n' 'Workspace creation/image-build smoke coverage is disabled because it requires a GitHub user token. The remaining Gitpod API smoke tests require explicit opt-in and separate Gitpod credentials; this workflow does not enable them. A run containing only skipped tests provides no functional smoke coverage.' >> "$GITHUB_STEP_SUMMARY" - id: auth if: failure() uses: google-github-actions/auth@955352c3b43196640b567e4646256d2fbb4aa1c7 # pin@v1 diff --git a/.github/workflows/workspace-integration-tests.yml b/.github/workflows/workspace-integration-tests.yml index 3d61ac2737a3cd..db6e6b6cc5f10b 100644 --- a/.github/workflows/workspace-integration-tests.yml +++ b/.github/workflows/workspace-integration-tests.yml @@ -5,6 +5,14 @@ permissions: on: workflow_dispatch: inputs: + test_suite: + required: false + type: choice + description: "Integration suite with retained functional coverage" + options: + - workspace + - webapp + default: workspace name: required: false type: string @@ -168,14 +176,12 @@ jobs: uses: ./.github/actions/integration-tests with: preview_name: ${{ needs.configuration.outputs.name }} - test_suite: workspace + test_suite: ${{ inputs.test_suite || 'workspace' }} notify_slack_webhook: ${{ secrets.WORKSPACE_SLACK_WEBHOOK }} github_token: ${{ secrets.GITHUB_TOKEN }} identity_provider: ${{ secrets.DEV_PREVIEW_PROVIDER }} service_account: ${{ secrets.DEV_PREVIEW_SA }} leeway_segment_key: ${{ secrets.LEEWAY_SEGMENT_KEY }} - integration_test_username: ${{ secrets.WORKSPACE_INTEGRATION_TEST_USERNAME }} - integration_test_usertoken: ${{ secrets.WORKSPACE_INTEGRATION_TEST_USER_TOKEN }} delete: name: Delete preview environment diff --git a/dev/jetbrains-test/README.md b/dev/jetbrains-test/README.md index c4f3cb566bfb1e..5bca99267dcacf 100644 --- a/dev/jetbrains-test/README.md +++ b/dev/jetbrains-test/README.md @@ -1,30 +1,13 @@ -## JetBrains Intergration Test +# JetBrains Integration Tests -See also [Internal Document](https://www.notion.so/gitpod/IDE-Integration-Tests-350235cc0db7489e86ebb57488a91f78) +All JetBrains integration tests are disabled because they depend on a GitHub +user token. The test entry points remain as explicit skips; supplying a token +does not enable them. Their previous implementations are available in Git history. -### How to trigger it manually? +`leeway run test:dev-intellij` reports this status without setting up GUI tools, +creating a preview, or requesting credentials. The IDE integration workflow +retains deployment/readiness checks and skipped-test reporting, but provides +no functional IDE coverage. -#### 1. With GHA - -- Trigger https://github.com/gitpod-io/gitpod/actions/workflows/ide-integration-tests.yml - -#### 2. In workspace with GHA - -- Create a preview env -```sh -TF_VAR_infra_provider=gce TF_VAR_with_large_vm=true leeway run dev:preview -``` -- Start tests -```sh -cd test/tests/ide/jetbrains -go test -v ./... -kubeconfig=/home/gitpod/.kube/config -namespace=default -username= -``` - -#### 3. In workspace - -- Open with Gitpod -- Create a preview env -```sh -TF_VAR_infra_provider=gce TF_VAR_with_large_vm=true leeway run dev:preview -``` -- Exec `leeway run test:dev-intellij` +See [the integration test documentation](../../test/README.md) for the disabled +tests and the workspace/component coverage that remains. diff --git a/test/BUILD.yaml b/test/BUILD.yaml index 6a40722a6c49b7..2ab79a5ddbc5d9 100644 --- a/test/BUILD.yaml +++ b/test/BUILD.yaml @@ -9,7 +9,6 @@ packages: - go.mod - go.sum - "**/*.go" - - tests/ide/jetbrains/warmup-indexing.sh - leeway-build.sh deps: - components/common-go:lib @@ -46,32 +45,7 @@ packages: - ${imageRepoBase}/integration-tests:commit-${__git_commit} scripts: - name: dev-intellij - description: Start IntelliJ IDEA intergration tests in workspace. + description: Report disabled IntelliJ IDEA integration coverage. script: | - which gp-vncsession &>/dev/null || leeway run dev/jetbrains-test:install-gui-dependencies - echo -e "📣 Access GUI on $(gp url 6080)\n\n" - [ "$(git rev-parse --abbrev-ref HEAD)" = "main" ] && echo "❌ Please create new branch" && exit 1 - - export DISPLAY=:0 - previewUrl=$(previewctl get url) - ok=true - useLatest=false - curl -s -o /dev/null -w "%{http_code}" "$previewUrl/api/version" --max-time 1 | grep -q "200" || { echo -e "❌ Preview env is not ready yet, try create one:\nTF_VAR_infra_provider="gce" TF_VAR_with_large_vm=true leeway run dev:preview\n"; exit 1; } - echo "✅ Preview env $previewUrl/workspaces is ready" - [ -z "$USER_TOKEN" ] && echo "❌ env USER_TOKEN is not set or is empty. Create one PAT $previewUrl/user/tokens" && ok=false || echo "✅ PAT is set" - [ -z "$USERNAME" ] && echo "❌ env USERNAME is not set" && ok=false || echo "✅ User is set" - $ok || exit 1; - - options=("latest" "stable") - echo "Select editor version: 1) latest[default]; 2) stable: " - read -p "$REPLY" choice - choice=${choice:-1} - option=${options[choice-1]} - useLatest=$([ "$option" == "latest" ] && true || false) - - echo "🚢 Starting intergration tests for IntelliJ $option..." - if [ "$DEBUG" = "true" ]; then - TEST_USE_LATEST_VERSION=$useLatest TEST_IN_WORKSPACE=true ROBOQUAT_TOKEN=skip dlv test /workspace/gitpod/test/tests/ide/jetbrains --headless --listen=:32991 --api-version=2 -- -test.timeout=60m -test.v -test.run=^TestIntelliJWarmup -kubeconfig=$HOME/.kube/config -namespace=default -username=$USERNAME - else - TEST_USE_LATEST_VERSION=$useLatest TEST_IN_WORKSPACE=true ROBOQUAT_TOKEN=skip go test -timeout 60m -v -run ^TestGoLand ./tests/ide/jetbrains -kubeconfig=$HOME/.kube/config -namespace=default -username=$USERNAME - fi + echo "JetBrains integration tests are disabled because they depend on a GitHub user token." + echo "No IDE functionality is tested. See test/README.md for retained integration coverage." diff --git a/test/README.md b/test/README.md index 7612c89e9aadb3..0d862c9da9c5fa 100644 --- a/test/README.md +++ b/test/README.md @@ -24,9 +24,8 @@ Such tests are for example: You can opt-in to run the integrations tests as part of the build job. that runs the integration tests against preview environments. - > For tests that require an existing user the framework tries to automatically select one from the DB. - > - On preview envs make sure to create one before running tests against it! - > - If it's important to use a certain user (with fixed settings, for example) pass the additional `username` parameter. + > Retained tests use builtin or temporary users by default. An explicitly selected + > `username` must already exist in the preview database. Example command: @@ -34,6 +33,10 @@ Example command: werft job run github -a with-preview=true -a with-integration-tests=webapp -f ``` +The GitHub Actions **Workspace integration tests** workflow also supports manual +runs of either `workspace` or `webapp` via its `test_suite` input. Scheduled runs +and reusable workflow calls continue to run `workspace`. + ## Manually You may want to run tests to assert whether a Gitpod installation is successfully integrated. @@ -47,12 +50,13 @@ This is best for when you're actively developing Gitpod. Test will work if images that they use are already cached by Gitpod instance. If not, they might fail if it takes too long to pull an image. -There are 4 different types of tests: +The default suites use builtin or temporary Gitpod users. They do not require a +GitHub user token or a pre-existing GitHub-authenticated test user. Some retained +tests still clone public repositories anonymously and pull container images. -1. Enterprise specific, that require valid license to be installed. Run those with `-enterprise=true` -2. Tests that require correct user (user should have github OAuth integration setup with gitpod). Run those with `-username=`. Make sure to load https://github.com/gitpod-io/gitpod-test-repo and https://github.com/gitpod-io/gitpod workspaces inside your gitpod that you are testing to preload those images onto your node. Wait for it to finish pulling those image, this will ensure that test will not fail due to timeout while waiting to pull an image for the first time. -3. To test gitlab integration, add `-gitlab=true` -4. All other tests. +Enterprise-specific tests retain their `-enterprise=true` opt-in. An explicit +`USER_NAME` or `-username` remains available for tests that support selecting an +existing Gitpod user, but the runner no longer fetches a user or token from secrets. If you want to run an entire test suite, the easiest is to use `./test/run.sh`: @@ -67,30 +71,92 @@ If you want to run an entire test suite, the easiest is to use `./test/run.sh`: ./test/run.sh -s webapp -r report.csv ``` -If you're iterating on a single test, the easiest is to use `go test` directly. - -If your integration tests depends on having having a user token available, then you'll have to set `USER_NAME` and `USER_TOKEN` environment variables. This can be done a couple ways: -1. Get credentials persisted as secrets (either in Github Actions, or GCP Secret Manager via the `core-dev` project), which vary by job that trigger tests. Refer to `run.sh` for details. -2. In your Gitpod (preview) environment, log into the preview environment, set `USER_NAME` to the user you logged in with, and set `USER_TOKEN` to any (does not have to be valid). +If you're iterating on a single retained test: ```console cd test -go test -v ./... \ - -run \ +go test -v ./tests/workspace \ + -kubeconfig=/home/gitpod/.kube/config \ -namespace=default \ - -username= \ - -enterprise= \ - -gitlab= + -run '^TestLaunchWorkspaceDirectly$' ``` -A concrete example would be - -```console -cd test -go test -v ./... \ - -namespace=default \ - -run TestWorkspaceInstrumentation -``` +Package setup still checks Kubernetes/Gitpod readiness before running tests, +including packages whose tests are all skipped. Use `go test -c` to compile a +package without executing that setup. + +## Disabled GitHub user-token coverage + +The following test entry points are explicit skip stubs. Providing a token does +not re-enable them. Their previous implementations are available in Git history. + +| Suite/package | Disabled tests | +|:--------------|:---------------| +| Workspace: ws-manager | `TestDotfiles` | +| Workspace: ws-daemon | `TestNetworkLimiting` | +| Workspace: runtime | `TestGitHubContexts`, `TestGitLabContexts`, `TestDiskActions`, `TestWorkspaceInstrumentation`, `TestGitHooks`, `TestGitActions`, `TestRegularWorkspacePorts`, `TestProcessPriority` | +| IDE: SSH | `TestSSHGatewayConnection` | +| IDE: VS Code | `TestPythonExtWorkspace` | +| IDE: JetBrains | `TestGoLand`, `TestIntellij`, `TestPhpStorm`, `TestPyCharm`, `TestRubyMine`, `TestWebStorm`, `TestRider`, `TestCLion`, `TestRustRover`, `TestIntellijNotPreconfiguredRepo`, `TestIntelliJWarmup` | +| Smoke | `TestStartWorkspaceWithImageBuild` | + +GitLab context tests shared the GitHub user-token fixture; this does not mean a +GitHub token authenticates to GitLab. Disk-quota tests indirectly relied on the +shared authenticated user, despite having no token guard of their own. + +CI no longer supplies the GitHub test-user credentials, and `run.sh` no longer +loads them from CI environment variables or the Kubernetes test-user secret. +Gitpod API tokens generated inside the test framework remain available. + +## Remaining component coverage + +This table counts enabled top-level test entry points retained after removing the +GitHub user-token dependency. It is not line or branch coverage. Already skipped +or opt-in tests are excluded from the counts. + +| Component/area | Tests before → after | Retained | Remaining checks | +|:---------------|:---------------------|:---------|:-----------------| +| ws-manager | 14 → 13 | 93% | Lifecycle, backups, maintenance, repositories, Git status, tasks, protected secrets, prebuilds | +| ws-daemon | 5 → 4 | 80% | CPU burst, I/O limits, FUSE, bucket creation | +| content-service | 3 → 3 | 100% | Upload/download URLs and blob round trips | +| image-builder | 2 → 2 | 100% | Base-image builds and concurrent builds | +| server (`webapp`) | 2 → 1 | 50% | Authenticated `GetLoggedInUser` | +| database (`webapp`) | 1 → 1 | 100% | Builtin workspace user exists | +| Workspace runtime | 14 → 7 | 50% | Direct launch, cgroups, process limits, ephemeral storage, `/proc`, Docker, `gp top` | +| JetBrains / VS Code / SSH IDE suites | 13 → 0 | 0% | None | +| Default workspace/image-build smoke flow | 1 → 0 | 0% | None | + +The workspace suite still runs its regular and maintenance passes. It retains 30 +top-level test functions, including the already-skipped K3s test. Other existing +conditions, such as Docker Hub rate limiting, can affect actual execution. +`TestLaunchWorkspaceDirectly` remains active alongside the disabled +`TestWorkspaceInstrumentation` in the same source file. + +The `webapp` suite's `TestStartWorkspace` is retained for explicitly configured +users, but skips by default without a username; it still needs a GitHub context. +`TestAdminBlockUser` requires the enterprise flag. Consequently the default webapp +checks are `TestServerAccess` and `TestBuiltinUserExists`. + +IDE workflows have no active functional tests. The default preview-regression +smoke workflow also has no active functional tests. Their deployment/readiness +checks and skipped-test reports do not validate IDE, SSH gateway, or user-facing +workspace-creation behavior. The workflows report these limitations explicitly. + +## Opt-in smoke tests using Gitpod credentials + +These tests do not use the removed GitHub credential and remain available through +`go test` in `test/tests/smoke-test`: + +- `TestMembers`, `TestProjects`, and `TestGetProject` use a **Gitpod PAT or session + cookie** supplied as `USER_TOKEN`, with `TEST_COLLABORATOR=true`. See + [collaborator_test.go](tests/smoke-test/collaborator_test.go) for setup. +- The six `TestCreateTemporaryAccessToken*` tests use `INSTALLATION_ADMIN_PAT` + and/or `MEMBER_USER_PAT`, with `TEST_CREATE_TMP_TOKEN=true`. See + [papi_create_temp_token_test.go](tests/smoke-test/papi_create_temp_token_test.go). + +These opt-in flags are not enabled by the default smoke workflow. GitHub Actions' +`GITHUB_TOKEN` and other infrastructure credentials are separate from both these +Gitpod credentials and the removed GitHub user credential. # Tips diff --git a/test/pkg/integration/apis.go b/test/pkg/integration/apis.go index a736e723c3bb2f..620d57bac75593 100644 --- a/test/pkg/integration/apis.go +++ b/test/pkg/integration/apis.go @@ -15,7 +15,6 @@ import ( "database/sql" "encoding/base64" "encoding/json" - "errors" "fmt" "io" "net" @@ -459,115 +458,6 @@ func (c *ComponentAPI) UpdateUserFeatureFlag(userId, featureFlag string) error { return nil } -func (c *ComponentAPI) CreateUser(username string, token string) (string, error) { - dbConfig, err := FindDBConfigFromPodEnv("server", c.namespace, c.client) - if err != nil { - return "", err - } - - db, err := c.DB() - if err != nil { - return "", err - } - - var userId string - err = db.QueryRow(`SELECT id FROM d_b_user WHERE name = ? and markedDeleted != 1 and blocked != 1`, username).Scan(&userId) - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return "", err - } - - if userId == "" { - userUuid, err := uuid.NewRandom() - if err != nil { - return "", err - } - - userId = userUuid.String() - _, err = db.Exec(`INSERT IGNORE INTO d_b_user (id, creationDate, avatarUrl, name, fullName, featureFlags, lastVerificationTime) VALUES (?, ?, ?, ?, ?, ?, ?)`, - userId, - time.Now().Format(time.RFC3339), - "", - username, - username, - "{\"permanentWSFeatureFlags\":[]}", - time.Now().Format(time.RFC3339), - ) - if err != nil { - return "", err - } - } - - var authId string - err = db.QueryRow(`SELECT authId FROM d_b_identity WHERE userId = ?`, userId).Scan(&authId) - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return "", err - } - if authId == "" { - authId = strconv.FormatInt(time.Now().UnixMilli(), 10) - _, err = db.Exec(`INSERT IGNORE INTO d_b_identity (authProviderId, authId, authName, userId) VALUES (?, ?, ?, ?)`, - "Public-GitHub", - authId, - username, - userId, - ) - if err != nil { - return "", err - } - } - - var cnt int - err = db.QueryRow(`SELECT COUNT(1) AS cnt FROM d_b_token_entry WHERE authId = ?`, authId).Scan(&cnt) - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return "", err - } - if cnt == 0 { - uid, err := uuid.NewRandom() - if err != nil { - return "", err - } - - // Double Marshalling to be compatible with EncryptionServiceImpl - value := struct { - Value string `json:"value"` - Scopes []string `json:"scopes"` - }{ - Value: token, - Scopes: []string{"user:email", "read:user", "public_repo"}, - } - valueBytes, err := json.Marshal(value) - if err != nil { - return "", err - } - valueBytes2, err := json.Marshal(string(valueBytes)) - if err != nil { - return "", err - } - - encryptedData, iv := EncryptValue(valueBytes2, dbConfig.EncryptionKeys.Material) - encrypted := EncriptedDBData{} - encrypted.Data = encryptedData - encrypted.KeyParams.Iv = iv - encrypted.KeyMetadata.Name = dbConfig.EncryptionKeys.Metadata.Name - encrypted.KeyMetadata.Version = dbConfig.EncryptionKeys.Metadata.Version - encryptedJson, err := json.Marshal(encrypted) - if err != nil { - return "", err - } - - _, err = db.Exec(`INSERT IGNORE INTO d_b_token_entry (authProviderId, authId, token, uid) VALUES (?, ?, ?, ?)`, - "Public-GitHub", - authId, - encryptedJson, - uid.String(), - ) - if err != nil { - return "", err - } - } - - return userId, nil -} - func (c *ComponentAPI) createGitpodToken(user string, scopes []string) (tkn string, err error) { id, err := c.GetUserId(user) if err != nil { diff --git a/test/pkg/integration/disk-client.go b/test/pkg/integration/disk-client.go index a5c1ebda50c8af..72a90567a58f71 100644 --- a/test/pkg/integration/disk-client.go +++ b/test/pkg/integration/disk-client.go @@ -36,7 +36,7 @@ func (d DiskClient) Fallocate(testFilePath string, spaceToAllocate string) error return fmt.Errorf("returned returned rc: %d err: %v", resp.ExitCode, resp.Stderr) } if strings.Contains(resp.Stdout, NoSpaceErrorMsg) { - return fmt.Errorf(resp.Stdout) + return fmt.Errorf("%s", resp.Stdout) } return nil diff --git a/test/pkg/integration/setup.go b/test/pkg/integration/setup.go index 6b31e30ac3146f..98be29dd8fbc68 100644 --- a/test/pkg/integration/setup.go +++ b/test/pkg/integration/setup.go @@ -32,12 +32,6 @@ func SkipWithoutUsername(t *testing.T, username string) { } } -func SkipWithoutUserToken(t *testing.T, userToken string) { - if userToken == "" { - t.Skip("Skipping because requires a user token") - } -} - func SkipWithoutEnterpriseLicense(t *testing.T, enterpise bool) { if !enterpise { t.Skip("Skipping because requires enterprise license") @@ -249,7 +243,7 @@ func logGitpodStatus(t *testing.T, client klient.Client, namespace string) { } } tw.Flush() - t.Logf("Gitpod components status:\n" + buf.String()) + t.Logf("Gitpod components status:\n%s", buf.String()) } func isPreviewReady(client klient.Client, namespace string) (ready bool, reason string, err error) { diff --git a/test/run.sh b/test/run.sh index a869d27fcb13b7..0d4fcfe2e9a7b6 100755 --- a/test/run.sh +++ b/test/run.sh @@ -75,6 +75,12 @@ case $TEST_SUITE in exit 1 esac +case $TEST_SUITE in + ide|jetbrains|vscode|ssh|all|"") + echo "IDE integration tests are disabled: skipped tests provide no IDE or SSH gateway coverage. See test/README.md." + ;; +esac + args=() if [ "${REPORT}" != "" ]; then args+=( "--json" ) @@ -83,21 +89,8 @@ args+=( "-kubeconfig=${KUBECONFIG:-/home/gitpod/.kube/config}" ) args+=( "-namespace=${NAMESPACE:-default}" ) args+=( "-timeout=120m" ) -if [[ "${GITPOD_REPO_ROOT:-}" != "" ]]; then - echo "Running in Gitpod workspace. Fetching USER_NAME and USER_TOKEN" - USER_NAME="$(kubectl --context=dev -n werft get secret integration-test-user -o jsonpath='{.data.username}' | base64 -d)" - USER_TOKEN="$(kubectl --context=dev -n werft get secret integration-test-user -o jsonpath='{.data.token}' | base64 -d)" - export USER_NAME - export USER_TOKEN -else - echo "Using INTEGRATION_TEST_USERNAME and INTEGRATION_TEST_USER_TOKEN for USER_NAME and USER_TOKEN" - USER_NAME="${INTEGRATION_TEST_USERNAME}" - USER_TOKEN="${INTEGRATION_TEST_USER_TOKEN}" - export USER_NAME - export USER_TOKEN -fi - -[[ "$USER_NAME" != "" ]] && args+=( "-username=$USER_NAME" ) +# Tests use builtin or temporary Gitpod users unless one is explicitly selected. +[[ -n "${USER_NAME:-}" ]] && args+=( "-username=$USER_NAME" ) go install github.com/jstemmer/go-junit-report/v2@latest diff --git a/test/tests/components/ws-daemon/network_limiting_test.go b/test/tests/components/ws-daemon/network_limiting_test.go index a2b1905cf77ee8..2958cc1cf0c479 100644 --- a/test/tests/components/ws-daemon/network_limiting_test.go +++ b/test/tests/components/ws-daemon/network_limiting_test.go @@ -4,101 +4,8 @@ package wsdaemon -import ( - "context" - "os" - "testing" - "time" - - "github.com/gitpod-io/gitpod/common-go/kubernetes" - daemon "github.com/gitpod-io/gitpod/test/pkg/agent/daemon/api" - "github.com/gitpod-io/gitpod/test/pkg/integration" - corev1 "k8s.io/api/core/v1" - "sigs.k8s.io/e2e-framework/pkg/envconf" - "sigs.k8s.io/e2e-framework/pkg/features" -) +import "testing" func TestNetworkLimiting(t *testing.T) { - userToken, _ := os.LookupEnv("USER_TOKEN") - integration.SkipWithoutUsername(t, username) - integration.SkipWithoutUserToken(t, userToken) - - f := features.New("network limiting"). - WithLabel("component", "ws-daemon"). - Assess("verify if network limiting works fine", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - t.Parallel() - - ctx, cancel := context.WithTimeout(testCtx, 5*time.Minute) - defer cancel() - - api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) - t.Cleanup(func() { - api.Done(t) - }) - - _, err := api.CreateUser(username, userToken) - if err != nil { - t.Fatal(err) - } - - ws, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, "https://github.com/gitpod-io/empty", username, api, integration.WithGitpodUser(username)) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { - sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer scancel() - - sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer sapi.Done(t) - - _, err = stopWs(true, sapi) - if err != nil { - t.Fatal(err) - } - }) - - daemonClient, daemonCloser, err := integration.Instrument(integration.ComponentWorkspaceDaemon, "daemon", cfg.Namespace(), kubeconfig, cfg.Client(), - integration.WithWorkspacekitLift(false), - integration.WithContainer("ws-daemon"), - ) - if err != nil { - t.Fatalf("unexpected error instrumenting daemon: %v", err) - } - defer daemonClient.Close() - integration.DeferCloser(t, daemonCloser) - - t.Logf("checking if workspace pod has network limit annotation") - var pod corev1.Pod - if err := cfg.Client().Resources().Get(ctx, "ws-"+ws.LatestInstance.ID, cfg.Namespace(), &pod); err != nil { - t.Fatal(err) - } - annotation, ok := pod.Annotations[kubernetes.WorkspaceNetConnLimitAnnotation] - if !ok { - t.Fatalf("expected annotation %s to be present on workspace pod but wasn't", kubernetes.WorkspaceNetConnLimitAnnotation) - } - if annotation != "true" { - t.Fatalf("expected annotation %s to be true but was %s", kubernetes.WorkspaceNetConnLimitAnnotation, annotation) - } - - t.Logf("checking nftable rules for rate limiting") - containerId := getCalicoContainerId(&pod) - var resp daemon.VerifyRateLimitingRuleResponse - err = daemonClient.Call("DaemonAgent.VerifyRateLimitingRule", daemon.VerifyRateLimitingRuleRequest{ - ContainerId: containerId, - }, &resp) - if err != nil { - t.Errorf("error verifying rate limiting rule for container %s: %v", containerId, err) - } - - t.Logf("verified rate limiting rule") - - return testCtx - }).Feature() - - testEnv.Test(t, f) -} - -func getCalicoContainerId(pod *corev1.Pod) string { - return pod.Annotations["cni.projectcalico.org/containerID"] + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } diff --git a/test/tests/components/ws-manager/dotfiles_test.go b/test/tests/components/ws-manager/dotfiles_test.go index 0e4b5c8a6acce6..790e1638671f61 100644 --- a/test/tests/components/ws-manager/dotfiles_test.go +++ b/test/tests/components/ws-manager/dotfiles_test.go @@ -4,189 +4,8 @@ package wsmanager -import ( - "context" - "encoding/json" - "fmt" - "os" - "strings" - "testing" - "time" - - corev1 "k8s.io/api/core/v1" - "sigs.k8s.io/e2e-framework/klient" - "sigs.k8s.io/e2e-framework/pkg/envconf" - "sigs.k8s.io/e2e-framework/pkg/features" - - csapi "github.com/gitpod-io/gitpod/content-service/api" - agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" - "github.com/gitpod-io/gitpod/test/pkg/integration" - wsmanapi "github.com/gitpod-io/gitpod/ws-manager/api" -) +import "testing" func TestDotfiles(t *testing.T) { - userToken, _ := os.LookupEnv("USER_TOKEN") - integration.SkipWithoutUsername(t, username) - integration.SkipWithoutUserToken(t, userToken) - - f := features.New("dotfiles").WithLabel("component", "ws-manager").Assess("ensure dotfiles are loaded", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - t.Parallel() - - ctx, cancel := context.WithTimeout(testCtx, 5*time.Minute) - defer cancel() - - api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) - t.Cleanup(func() { - api.Done(t) - }) - - userId, err := api.CreateUser(username, userToken) - if err != nil { - t.Fatal(err) - } - - // Scopes should larger than https://github.com/gitpod-io/gitpod/blob/main/components/supervisor/pkg/serverapi/publicapi.go#L99-L109 - tokenId, err := api.CreateOAuth2Token(username, []string{ - "function:getToken", - "function:openPort", - "function:getOpenPorts", - "function:guessGitTokenScopes", - "function:getWorkspace", - "function:sendHeartBeat", - "function:trackEvent", - "resource:token::*::get", - }) - if err != nil { - t.Fatal(err) - } - - swr := func(req *wsmanapi.StartWorkspaceRequest) error { - req.Spec.Envvars = append(req.Spec.Envvars, - &wsmanapi.EnvironmentVariable{ - Name: "SUPERVISOR_DOTFILE_REPO", - Value: "https://github.com/gitpod-io/test-dotfiles-support", - }, - &wsmanapi.EnvironmentVariable{ - Name: "THEIA_SUPERVISOR_TOKENS", - Value: fmt.Sprintf(`[{ - "token": "%v", - "kind": "gitpod", - "host": "%v", - "scope": ["function:getToken", "function:openPort", "function:sendHeartBeat", "function:getOpenPorts", "function:guessGitTokenScopes", "function:getWorkspace", "function:trackEvent", "resource:token::*::get"], - "expiryDate": "2026-10-26T10:38:05.232Z", - "reuse": 4 - }]`, tokenId, getHostUrl(ctx, t, cfg.Client(), cfg.Namespace())), - }, - ) - - req.Spec.Initializer = &csapi.WorkspaceInitializer{ - Spec: &csapi.WorkspaceInitializer_Git{ - Git: &csapi.GitInitializer{ - RemoteUri: "https://github.com/gitpod-io/empty", - CheckoutLocation: "empty", - Config: &csapi.GitConfig{}, - }, - }, - } - - req.Metadata.Owner = userId - req.Spec.WorkspaceLocation = "empty" - return nil - } - - ws, stopWs, err := integration.LaunchWorkspaceDirectly(t, ctx, api, integration.WithRequestModifier(swr)) - if err != nil { - t.Fatal(err) - } - - defer func() { - sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer scancel() - - sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer sapi.Done(t) - - _, err = stopWs(true, sapi) - if err != nil { - t.Errorf("cannot stop workspace: %q", err) - } - }() - - rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), - integration.WithInstanceID(ws.Req.Id), - integration.WithContainer("workspace"), - integration.WithWorkspacekitLift(true), - ) - if err != nil { - t.Fatal(err) - } - - integration.DeferCloser(t, closer) - defer rsa.Close() - - assertDotfiles(t, rsa) - - return testCtx - }).Feature() - - testEnv.Test(t, f) -} - -func getHostUrl(ctx context.Context, t *testing.T, k8sClient klient.Client, namespace string) string { - var configmap corev1.ConfigMap - if err := k8sClient.Resources().Get(ctx, "server-config", namespace, &configmap); err != nil { - t.Fatal(err) - } - - config, ok := configmap.Data["config.json"] - if !ok { - t.Fatal("server config map does not contain config.json") - } - - c := make(map[string]json.RawMessage) - if err := json.Unmarshal([]byte(config), &c); err != nil { - t.Fatal(err) - } - - hostUrlRaw, ok := c["hostUrl"] - if !ok { - t.Fatal("server config map does not contain host url") - } - - return strings.TrimPrefix(strings.Trim(string(hostUrlRaw), "\""), "https://") -} - -func assertDotfiles(t *testing.T, rsa *integration.RpcClient) error { - var ls agent.ListDirResponse - err := rsa.Call("WorkspaceAgent.ListDir", &agent.ListDirRequest{ - Dir: "/home/gitpod/.dotfiles", - }, &ls) - - if err != nil { - t.Fatal(err) - } - - dotfiles := map[string]bool{ - "bash_aliases": false, - "git": false, - } - - for _, dir := range ls.Files { - delete(dotfiles, dir) - } - - if len(dotfiles) > 0 { - var cat agent.ExecResponse - err := rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ - Dir: "/", - Command: "cat", - Args: []string{"/home/gitpod/.dotfiles.log"}, - }, &cat) - if err == nil { - t.Fatalf("dotfiles were not installed successfully: %+v, .dotfiles.log: %s", dotfiles, cat.Stdout) - } - t.Fatalf("dotfiles were not installed successfully: %+v", dotfiles) - } - - return nil + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } diff --git a/test/tests/ide/jetbrains/base_in_workspace_test.go b/test/tests/ide/jetbrains/base_in_workspace_test.go deleted file mode 100644 index c6254b46fa89dc..00000000000000 --- a/test/tests/ide/jetbrains/base_in_workspace_test.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2024 Gitpod GmbH. All rights reserved. -// Licensed under the GNU Affero General Public License (AGPL). -// See License.AGPL.txt in the project root for license information. - -package ide - -import ( - "context" - "fmt" - "io" - "os" - "os/exec" - "testing" -) - -type testLogWriter struct { - t *testing.T -} - -var _ io.Writer = &testLogWriter{} - -func (t *testLogWriter) Write(p []byte) (n int, err error) { - t.t.Log(string(p)) - return len(p), nil -} - -const localDebug = false - -func testWithoutGithubAction(ctx context.Context, gatewayLink, gitpodAccessToken, secretEndpoint string, useLatest bool) { - scriptName := "dev/jetbrains-test:test-stable" - if useLatest { - scriptName = "dev/jetbrains-test:test-latest" - } - - if localDebug { - fmt.Printf("Exec command below to run UI tests:\n\nexport DISPLAY=:0\nexport GATEWAY_LINK=\"%s\"\nexport GITPOD_TEST_ACCESSTOKEN=\"%s\"\nexport WS_ENDPOINT=%s\nleeway run %s -Dversion=integration-test -DpublishToJBMarketplace=false", gatewayLink, gitpodAccessToken, secretEndpoint, scriptName) - os.Exit(1) - } - cmdEnv := os.Environ() - cmdEnv = append(cmdEnv, "GATEWAY_LINK="+gatewayLink) - cmdEnv = append(cmdEnv, "GITPOD_TEST_ACCESSTOKEN="+gitpodAccessToken) - cmdEnv = append(cmdEnv, "WS_ENDPOINT="+secretEndpoint) - cmd := exec.CommandContext(ctx, "leeway", "run", scriptName, "-Dversion=integration-test", "-DpublishToJBMarketplace=false") - cmd.Env = cmdEnv - // writer := &testLogWriter{t: t} - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stdout - cmd.Run() -} diff --git a/test/tests/ide/jetbrains/base_test.go b/test/tests/ide/jetbrains/base_test.go deleted file mode 100644 index 82df464eb77fb1..00000000000000 --- a/test/tests/ide/jetbrains/base_test.go +++ /dev/null @@ -1,412 +0,0 @@ -// Copyright (c) 2024 Gitpod GmbH. All rights reserved. -// Licensed under the GNU Affero General Public License (AGPL). -// See License.AGPL.txt in the project root for license information. - -package ide - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "regexp" - "strings" - "testing" - "time" - - "golang.org/x/oauth2" - "sigs.k8s.io/e2e-framework/pkg/envconf" - - protocol "github.com/gitpod-io/gitpod/gitpod-protocol" - supervisor "github.com/gitpod-io/gitpod/supervisor/api" - agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" - "github.com/gitpod-io/gitpod/test/pkg/integration" - wsmanapi "github.com/gitpod-io/gitpod/ws-manager/api" - "github.com/google/go-github/v42/github" -) - -var testBaseConfig = map[string]struct{ ProductCode, Repo string }{ - "goland": {"GO", "https://github.com/gitpod-samples/template-golang-cli"}, - "intellij": {"IU", "https://github.com/jeanp413/spring-petclinic"}, - "phpstorm": {"PS", "https://github.com/gitpod-samples/template-php-laravel-mysql"}, - "pycharm": {"PY", "https://github.com/gitpod-samples/template-python-django"}, - // TODO: open comment after https://github.com/gitpod-io/gitpod/issues/16302 resolved - // "rubymine": {"RM", "https://github.com/gitpod-samples/template-ruby-on-rails-postgres"}, - "rubymine": {"RM", "https://github.com/gitpod-io/Gitpod-Ruby-On-Rails"}, - "webstorm": {"WS", "https://github.com/gitpod-samples/template-typescript-react"}, - "rider": {"RD", "https://github.com/gitpod-samples/template-dotnet-core-cli-csharp"}, - "clion": {"CL", "https://github.com/gitpod-samples/template-cpp"}, - "rustrover": {"RR", "https://github.com/gitpod-samples/template-rust-cli"}, -} - -var ( - userToken string - roboquatToken string -) - -func init() { - userToken, _ = os.LookupEnv("USER_TOKEN") - roboquatToken, _ = os.LookupEnv("ROBOQUAT_TOKEN") -} - -type JetBrainsIDETestOpts struct { - IDE string - ProductCode string - Repo string - AdditionalRpcCalls []func(rsa *integration.RpcClient, jbCtx *JetBrainsTestCtx) error - BeforeWorkspaceStart func(userID string) error - RepositoryID string -} - -type JetBrainsIDETestOpt func(*JetBrainsIDETestOpts) error - -func WithAdditionRpcCall(f func(rsa *integration.RpcClient, jbCtx *JetBrainsTestCtx) error) JetBrainsIDETestOpt { - return func(o *JetBrainsIDETestOpts) error { - o.AdditionalRpcCalls = append(o.AdditionalRpcCalls, f) - return nil - } -} - -func WithIDE(ide string) JetBrainsIDETestOpt { - return func(o *JetBrainsIDETestOpts) error { - o.IDE = ide - if t, ok := testBaseConfig[ide]; ok { - o.ProductCode = t.ProductCode - if o.Repo == "" { - o.Repo = t.Repo - } - } - return nil - } -} - -func WithRepo(repo string) JetBrainsIDETestOpt { - return func(o *JetBrainsIDETestOpts) error { - o.Repo = repo - return nil - } -} - -func WithRepositoryID(repoID string) JetBrainsIDETestOpt { - return func(o *JetBrainsIDETestOpts) error { - o.RepositoryID = repoID - return nil - } -} - -func BaseGuard(t *testing.T) { - integration.SkipWithoutUsername(t, username) - integration.SkipWithoutUserToken(t, userToken) - if roboquatToken == "" { - t.Fatal("this test need github action run permission") - } -} - -func JetBrainsIDETest(ctx context.Context, t *testing.T, cfg *envconf.Config, opts ...JetBrainsIDETestOpt) { - BaseGuard(t) - option := &JetBrainsIDETestOpts{} - for _, o := range opts { - if err := o(option); err != nil { - t.Fatal(err) - } - } - - api, server, _, _ := MustConnectToServer(ctx, t, cfg) - var err error - - t.Logf("starting workspace") - var info *protocol.WorkspaceInfo - var stopWs func(waitForStop bool, api *integration.ComponentAPI) (*wsmanapi.WorkspaceStatus, error) - useLatest := os.Getenv("TEST_USE_LATEST_VERSION") == "true" - for i := 0; i < 3; i++ { - info, stopWs, err = integration.LaunchWorkspaceWithOptions(t, ctx, &integration.LaunchWorkspaceOptions{ - ContextURL: option.Repo, - ProjectID: option.RepositoryID, - IDESettings: &protocol.IDESettings{ - DefaultIde: option.IDE, - UseLatestVersion: useLatest, - }, - }, username, api) - if err != nil { - if strings.Contains(err.Error(), "code 429 message: too many requests") { - t.Log(err) - time.Sleep(10 * time.Second) - continue - } - t.Fatal(err) - } else { - break - } - } - - defer func() { - sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer scancel() - - sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer sapi.Done(t) - - _, _ = stopWs(true, sapi) - }() - - t.Logf("get oauth2 token") - oauthToken, err := api.CreateOAuth2Token(username, []string{ - "function:getGitpodTokenScopes", - "function:getIDEOptions", - "function:getOwnerToken", - "function:getWorkspace", - "function:getWorkspaces", - "function:listenForWorkspaceInstanceUpdates", - "resource:default", - }) - if err != nil { - t.Fatal(err) - } - - t.Logf("resolve owner token") - ownerToken, err := server.GetOwnerToken(ctx, info.LatestInstance.WorkspaceID) - if err != nil { - t.Fatal(err) - } - - t.Logf("resolve desktop IDE link") - gatewayLink, err := resolveDesktopIDELink(info, option.IDE, ownerToken, t) - if err != nil { - t.Fatal(err) - } - - ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: roboquatToken}, - ) - tc := oauth2.NewClient(ctx, ts) - - githubClient := github.NewClient(tc) - - if os.Getenv("TEST_IN_WORKSPACE") == "true" { - t.Logf("run test in workspace") - go testWithoutGithubAction(ctx, gatewayLink, oauthToken, strings.TrimPrefix(info.LatestInstance.IdeURL, "https://"), useLatest) - } else { - t.Logf("trigger github action") - // Note: For manually trigger github action purpose - // t.Logf("secret_gateway_link %s\nsecret_access_token %s\nsecret_endpoint %s\njb_product %s\nuse_latest %v\nbuild_id %s\nbuild_url %s", gatewayLink, oauthToken, strings.TrimPrefix(info.LatestInstance.IdeURL, "https://"), option.IDE, useLatest, os.Getenv("TEST_BUILD_ID"), os.Getenv("TEST_BUILD_URL")) - // time.Sleep(30 * time.Minute) - _, err = githubClient.Actions.CreateWorkflowDispatchEventByFileName(ctx, "gitpod-io", "gitpod", "jetbrains-integration-test.yml", github.CreateWorkflowDispatchEventRequest{ - Ref: os.Getenv("TEST_BUILD_REF"), - Inputs: map[string]interface{}{ - "secret_gateway_link": gatewayLink, - "secret_access_token": oauthToken, - "secret_endpoint": strings.TrimPrefix(info.LatestInstance.IdeURL, "https://"), - "jb_product": option.IDE, - "use_latest": fmt.Sprintf("%v", useLatest), - "build_id": os.Getenv("TEST_BUILD_ID"), - "build_url": os.Getenv("TEST_BUILD_URL"), - }, - }) - if err != nil { - t.Fatal(err) - } - } - - checkUrl := fmt.Sprintf("https://63342-%s/codeWithMe/unattendedHostStatus?token=gitpod", strings.TrimPrefix(info.LatestInstance.IdeURL, "https://")) - - t.Logf("waiting result") - testStatus := false - for ctx.Err() == nil { - time.Sleep(1 * time.Second) - body, _ := getHttpContent(checkUrl, ownerToken) - var status gatewayHostStatus - err = json.Unmarshal(body, &status) - if err != nil { - continue - } - if len(status.Projects) == 1 && status.Projects[0].ControllerConnected { - testStatus = true - break - } - } - if !testStatus { - t.Fatal(ctx.Err()) - } - - rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), integration.WithInstanceID(info.LatestInstance.ID), integration.WithWorkspacekitLift(true)) - if err != nil { - t.Fatal(err) - } - defer rsa.Close() - integration.DeferCloser(t, closer) - - fatalMessages := []string{} - - checkIDEALogs := func() { - qualifier := "" - if useLatest { - qualifier = "-latest" - } - jbSystemDir := fmt.Sprintf("/workspace/.cache/JetBrains%s/RemoteDev-%s", qualifier, option.ProductCode) - ideaLogPath := jbSystemDir + "/log/idea.log" - - t.Logf("Check idea.log file correct location %s", ideaLogPath) - - var resp agent.ExecResponse - err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ - Dir: "/", - Command: "bash", - Args: []string{ - "-c", - fmt.Sprintf("cat %s", ideaLogPath), - }, - }, &resp) - - t.Logf("checking idea.log") - if err != nil || resp.ExitCode != 0 { - t.Fatal("idea.log file not found in the expected location") - } - - pluginLoadedRegex := regexp.MustCompile(`Loaded custom plugins:.* (Gitpod Remote|gitpod-remote)`) - pluginStartedRegex := regexp.MustCompile(`Gitpod gateway link`) - pluginIncompatibleRegex := regexp.MustCompile(`Plugin '(Gitpod Remote|gitpod-remote)' .* is not compatible`) - - ideaLogs := []byte(resp.Stdout) - if pluginLoadedRegex.Match(ideaLogs) { - t.Logf("backend-plugin loaded") - } else { - fatalMessages = append(fatalMessages, "backend-plugin not loaded") - } - if pluginStartedRegex.Match(ideaLogs) { - t.Logf("backend-plugin started") - } else { - fatalMessages = append(fatalMessages, "backend-plugin not started") - } - if pluginIncompatibleRegex.Match(ideaLogs) { - fatalMessages = append(fatalMessages, "backend-plugin is incompatible") - } else { - t.Logf("backend-plugin maybe compatible") - } - - for _, fn := range option.AdditionalRpcCalls { - if err := fn(rsa, &JetBrainsTestCtx{ - SystemDir: jbSystemDir, - }); err != nil { - fatalMessages = append(fatalMessages, fmt.Sprintf("additional agent exec failed: %v", err)) - } - } - } - checkIDEALogs() - - if len(fatalMessages) > 0 { - t.Fatalf("[error] tests fail: \n%s", strings.Join(fatalMessages, "\n")) - } -} - -func resolveDesktopIDELink(info *protocol.WorkspaceInfo, ide string, ownerToken string, t *testing.T) (string, error) { - var ( - ideLink string - err error - maxTries = 5 - ) - for i := 0; ideLink == "" && i < maxTries; i++ { - ideLink, err = fetchDekstopIDELink(info, ide, ownerToken) - if ideLink == "" && i < maxTries-1 { - t.Logf("failed to fetch IDE link: %v, trying again...", err) - } - } - if ideLink != "" { - return ideLink, nil - } - return "", err -} - -func fetchDekstopIDELink(info *protocol.WorkspaceInfo, ide string, ownerToken string) (string, error) { - body, err := getHttpContent(fmt.Sprintf("%s/_supervisor/v1/status/ide/wait/true", info.LatestInstance.IdeURL), ownerToken) - if err != nil { - return "", fmt.Errorf("failed to fetch IDE status response: %v", err) - } - - var ideStatus supervisor.IDEStatusResponse - err = json.Unmarshal(body, &ideStatus) - if err != nil { - return "", fmt.Errorf("failed to unmarshal IDE status response: %v, response body: %s", err, body) - } - if !ideStatus.GetOk() { - return "", fmt.Errorf("IDE status is not OK, response body: %s", body) - } - - desktop := ideStatus.GetDesktop() - if desktop == nil { - return "", fmt.Errorf("workspace does not have desktop IDE running, response body: %s", body) - } - if desktop.Kind != ide { - return "", fmt.Errorf("workspace does not have %s running, but %s, response body: %s", ide, desktop.Kind, body) - } - if desktop.Link == "" { - return "", fmt.Errorf("IDE link is empty, response body: %s", body) - } - return desktop.Link, nil -} - -func getHttpContent(url string, ownerToken string) ([]byte, error) { - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, err - } - req.Header.Set("x-gitpod-owner-token", ownerToken) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - b, err := io.ReadAll(resp.Body) - return b, err -} - -type gatewayHostStatus struct { - AppPid int64 `json:"appPid"` - AppVersion string `json:"appVersion"` - IdePath string `json:"idePath"` - Projects []struct { - BackgroundTasksRunning bool `json:"backgroundTasksRunning"` - ControllerConnected bool `json:"controllerConnected"` - GatewayLink string `json:"gatewayLink"` - HTTPLink string `json:"httpLink"` - JoinLink string `json:"joinLink"` - ProjectName string `json:"projectName"` - ProjectPath string `json:"projectPath"` - SecondsSinceLastControllerActivity int64 `json:"secondsSinceLastControllerActivity"` - Users []string `json:"users"` - } `json:"projects"` - RuntimeVersion string `json:"runtimeVersion"` - UnattendedMode bool `json:"unattendedMode"` -} - -type JetBrainsTestCtx struct { - UserID string - SystemDir string -} - -func MustConnectToServer(ctx context.Context, t *testing.T, cfg *envconf.Config) (*integration.ComponentAPI, protocol.APIInterface, *integration.PAPIClient, string) { - t.Logf("connected to server") - api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) - t.Cleanup(func() { - api.Done(t) - }) - t.Logf("get or create user") - userID, err := api.CreateUser(username, userToken) - if err != nil { - t.Fatal(err) - } - - t.Logf("connecting to server...") - server, err := api.GitpodServer(integration.WithGitpodUser(username)) - if err != nil { - t.Fatal(err) - } - t.Logf("connecting to papi...") - papi, err := api.PublicApi(integration.WithGitpodUser(username)) - if err != nil { - t.Fatal(err) - } - return api, server, papi, userID -} diff --git a/test/tests/ide/jetbrains/gateway_test.go b/test/tests/ide/jetbrains/gateway_test.go index bf32e4ecfbacd5..d9d454a86d6ff2 100644 --- a/test/tests/ide/jetbrains/gateway_test.go +++ b/test/tests/ide/jetbrains/gateway_test.go @@ -4,291 +4,48 @@ package ide -import ( - "context" - _ "embed" - "fmt" - "testing" - "time" - - "sigs.k8s.io/e2e-framework/pkg/envconf" - "sigs.k8s.io/e2e-framework/pkg/features" - - agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" - "github.com/gitpod-io/gitpod/test/pkg/integration" -) +import "testing" func TestGoLand(t *testing.T) { - BaseGuard(t) - t.Parallel() - f := features.New("Start a workspace using GoLand"). - WithLabel("component", "IDE"). - WithLabel("ide", "GoLand"). - Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) - defer cancel() - JetBrainsIDETest(ctx, t, cfg, WithIDE("goland")) - return testCtx - }). - Feature() - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } func TestIntellij(t *testing.T) { - BaseGuard(t) - t.Parallel() - f := features.New("Start a workspace using Intellij"). - WithLabel("component", "IDE"). - WithLabel("ide", "Intellij"). - Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) - defer cancel() - - JetBrainsIDETest(ctx, t, cfg, WithIDE("intellij")) - return testCtx - }). - Feature() - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } func TestPhpStorm(t *testing.T) { - BaseGuard(t) - t.Parallel() - f := features.New("Start a workspace using PhpStorm"). - WithLabel("component", "IDE"). - WithLabel("ide", "PhpStorm"). - Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) - defer cancel() - JetBrainsIDETest(ctx, t, cfg, WithIDE("phpstorm")) - return testCtx - }). - Feature() - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } func TestPyCharm(t *testing.T) { - BaseGuard(t) - t.Parallel() - f := features.New("Start a workspace using Pycharm"). - WithLabel("component", "IDE"). - WithLabel("ide", "Pycharm"). - Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) - defer cancel() - JetBrainsIDETest(ctx, t, cfg, WithIDE("pycharm")) - return testCtx - }). - Feature() - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } func TestRubyMine(t *testing.T) { - BaseGuard(t) - t.Parallel() - f := features.New("Start a workspace using RubyMine"). - WithLabel("component", "IDE"). - WithLabel("ide", "RubyMine"). - Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) - defer cancel() - JetBrainsIDETest(ctx, t, cfg, WithIDE("rubymine")) - return testCtx - }). - Feature() - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } func TestWebStorm(t *testing.T) { - BaseGuard(t) - t.Parallel() - f := features.New("Start a workspace using WebStorm"). - WithLabel("component", "IDE"). - WithLabel("ide", "WebStorm"). - Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) - defer cancel() - JetBrainsIDETest(ctx, t, cfg, WithIDE("webstorm")) - return testCtx - }). - Feature() - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } func TestRider(t *testing.T) { - BaseGuard(t) - t.Parallel() - f := features.New("Start a workspace using Rider"). - WithLabel("component", "IDE"). - WithLabel("ide", "Rider"). - Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) - defer cancel() - JetBrainsIDETest(ctx, t, cfg, WithIDE("rider")) - return testCtx - }). - Feature() - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } func TestCLion(t *testing.T) { - BaseGuard(t) - t.Parallel() - t.Skip("See EXP-414") - f := features.New("Start a workspace using CLion"). - WithLabel("component", "IDE"). - WithLabel("ide", "CLion"). - Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) - defer cancel() - JetBrainsIDETest(ctx, t, cfg, WithIDE("clion")) - return testCtx - }). - Feature() - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } func TestRustRover(t *testing.T) { - BaseGuard(t) - t.Parallel() - f := features.New("Start a workspace using RustRover"). - WithLabel("component", "IDE"). - WithLabel("ide", "RustRover"). - Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) - defer cancel() - JetBrainsIDETest(ctx, t, cfg, WithIDE("rustrover")) - return testCtx - }). - Feature() - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } func TestIntellijNotPreconfiguredRepo(t *testing.T) { - BaseGuard(t) - t.Parallel() - f := features.New("Start a workspace using Intellij with not preconfigured repo"). - WithLabel("component", "IDE"). - WithLabel("ide", "Intellij"). - Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) - defer cancel() - // ENT-260 - // https://github.com/spring-projects/spring-petclinic is not an option because it will prompt to ask user to select project type - // which will block integration test (UI tests) - JetBrainsIDETest(ctx, t, cfg, WithIDE("intellij"), WithRepo("https://github.com/gitpod-io/empty")) - return testCtx - }). - Feature() - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } -//go:embed warmup-indexing.sh -var warmupIndexingShell []byte - func TestIntelliJWarmup(t *testing.T) { - BaseGuard(t) - t.Parallel() - f := features.New("Start a workspace using Intellij and imagebuild to test warmup tasks"). - WithLabel("component", "IDE"). - WithLabel("ide", "Intellij"). - Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) - defer cancel() - - testRepo := "https://github.com/gitpod-samples/spring-petclinic" - testRepoBranch := "gp/integration-test" - - api, _, papi, _ := MustConnectToServer(ctx, t, cfg) - t.Logf("get or create team") - teamID, err := api.GetTeam(ctx, papi) - if err != nil { - t.Fatalf("failed to get or create team: %v", err) - } - t.Logf("get or create repository for %s", testRepo) - projectID, err := api.GetProject(ctx, papi, teamID, "petclinic", testRepo, true) - if err != nil { - t.Fatalf("failed to get or create project: %v", err) - } - - triggerAndWaitForPrebuild := func() error { - prebuildID, err := api.TriggerPrebuild(ctx, papi, projectID, testRepoBranch) - if err != nil { - return fmt.Errorf("failed to trigger prebuild: %v", err) - } - t.Logf("prebuild triggered, id: %s", prebuildID) - ok, err := api.WaitForPrebuild(ctx, papi, prebuildID) - if err != nil { - return fmt.Errorf("failed to wait for prebuild: %v", err) - } - if !ok { - return fmt.Errorf("prebuild failed") - } - // EXP-1860 - // Prebuild is marked as available before content back-up is completed - if err := api.WaitForPrebuildWorkspaceToStoppedPhase(ctx, prebuildID); err != nil { - return fmt.Errorf("failed to wait for prebuild workspace to be backed-up : %v", err) - } - return nil - } - - t.Logf("trigger prebuild and wait for it") - if err := triggerAndWaitForPrebuild(); err != nil { - t.Fatalf("failed to trigger prebuild: %v", err) - } - t.Logf("prebuild available") - - t.Logf("warmup prebuild prepared, org: %s, repository: %s", teamID, projectID) - - JetBrainsIDETest(ctx, t, cfg, WithIDE("intellij"), - WithRepo(fmt.Sprintf("%s/tree/%s", testRepo, testRepoBranch)), - WithRepositoryID(projectID), - WithAdditionRpcCall(func(rsa *integration.RpcClient, jbCtx *JetBrainsTestCtx) error { - t.Logf("check if it has warmup.log") - var resp agent.ExecResponse - err := rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ - Dir: "/", - Command: "bash", - Args: []string{ - "-c", - fmt.Sprintf("stat %s/log/warmup/warmup.log", jbCtx.SystemDir), - }, - }, &resp) - if err != nil { - return fmt.Errorf("warmup.log not found: %v", err) - } - if resp.ExitCode != 0 { - return fmt.Errorf("warmup.log not found: %s, %d", resp.Stderr, resp.ExitCode) - } - return nil - }), - WithAdditionRpcCall(func(rsa *integration.RpcClient, jbCtx *JetBrainsTestCtx) error { - t.Logf("sleep for 1 minute to wait project open") - var resp agent.ExecResponse - time.Sleep(1 * time.Minute) - t.Logf("checking warmup indexing") - err := rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ - Dir: "/", - Command: "bash", - Args: []string{ - "-c", - string(warmupIndexingShell), - "--", - jbCtx.SystemDir, - "1", - }, - }, &resp) - if err != nil { - return fmt.Errorf("failed to warmup indexing: %v", err) - } - t.Logf("stdout:\n%s", string(resp.Stdout)) - if resp.ExitCode != 0 { - return fmt.Errorf("failed to warmup indexing: %s, %d", resp.Stderr, resp.ExitCode) - } - return nil - })) - return testCtx - }). - Feature() - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } diff --git a/test/tests/ide/jetbrains/warmup-indexing.sh b/test/tests/ide/jetbrains/warmup-indexing.sh deleted file mode 100755 index 8bd7ee77ca7f70..00000000000000 --- a/test/tests/ide/jetbrains/warmup-indexing.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) 2024 Gitpod GmbH. All rights reserved. -# Licensed under the GNU Affero General Public License (AGPL). -# See License.AGPL.txt in the project root for license information. - -# This script is used to test JetBrains prebuild warmup indexing (search warmup-indexing.sh in codebase) -# It will get the last indexing json file (scan reason `On project open`) -# and check if the scheduled indexing count is greater than a specified threshold -# -# `exit 0` means JetBrains IDEs no need to indexing again -# Example: ./warmup-indexing.sh /workspace 1 - -set -euo pipefail -SystemDir=$1 -Threshold=$2 - -ProjectIndexingFolder=$(find "$SystemDir"/log/indexing-diagnostic -type d -name "spring*" -print -quit) -JsonFiles=$(find "$ProjectIndexingFolder" -type f -name "*.json") - -FilteredJsonFiles=() -for jsonFile in $JsonFiles; do - if jq -e '.projectIndexingActivityHistory.times.scanningReason == "On project open"' "$jsonFile" > /dev/null; then - FilteredJsonFiles+=("$jsonFile") - fi -done -mapfile -t sortedFiles < <(printf "%s\n" "${FilteredJsonFiles[@]}" | sort -r) - -targetFile=${sortedFiles[0]} -echo "Target indexing json file: $targetFile" -scheduledIndexing=$(jq '.projectIndexingActivityHistory.fileCount.numberOfFilesScheduledForIndexingAfterScan' "$targetFile") -echo "Scheduled indexing count: $scheduledIndexing, threshold: $Threshold" - -if [ "$scheduledIndexing" -gt "$Threshold" ]; then - echo "Error: Scheduled indexing count $scheduledIndexing > $Threshold" >&2 - exit 1 -else - exit 0 -fi diff --git a/test/tests/ide/ssh/ssh_gateway_test.go b/test/tests/ide/ssh/ssh_gateway_test.go index 6523b6df7e8b0f..e902492059f7f5 100644 --- a/test/tests/ide/ssh/ssh_gateway_test.go +++ b/test/tests/ide/ssh/ssh_gateway_test.go @@ -4,113 +4,8 @@ package ide -import ( - "context" - "io" - "log" - "net/url" - "os" - "strings" - "testing" - "time" - - "github.com/helloyi/go-sshclient" - "sigs.k8s.io/e2e-framework/pkg/envconf" - "sigs.k8s.io/e2e-framework/pkg/features" - - gitpod "github.com/gitpod-io/gitpod/gitpod-protocol" - "github.com/gitpod-io/gitpod/test/pkg/integration" -) +import "testing" func TestSSHGatewayConnection(t *testing.T) { - userToken, _ := os.LookupEnv("USER_TOKEN") - integration.SkipWithoutUsername(t, username) - integration.SkipWithoutUserToken(t, userToken) - - f := features.New("TestSSHGatewayConnection"). - WithLabel("component", "server"). - Assess("it can connect to a workspace via SSH gateway", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - ctx, cancel := context.WithTimeout(testCtx, 10*time.Minute) - defer cancel() - - api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) - t.Cleanup(func() { - api.Done(t) - }) - - _, err := api.CreateUser(username, userToken) - if err != nil { - t.Fatal(err) - } - - serverOpts := []integration.GitpodServerOpt{integration.WithGitpodUser(username)} - server, err := api.GitpodServer(serverOpts...) - if err != nil { - t.Fatal(err) - } - - // This env var caused an incident https://www.gitpodstatus.com/incidents/26gwnhcpvqqx before - // Which was introduced by PR https://github.com/gitpod-io/gitpod/pull/13822 - // And fixed by PR https://github.com/gitpod-io/gitpod/pull/13858 - _ = server.SetEnvVar(ctx, &gitpod.UserEnvVarValue{ - Name: "TEST", - RepositoryPattern: "*/*", - Value: "\\\"test space\\\"", - }) - - _ = server.SetEnvVar(ctx, &gitpod.UserEnvVarValue{ - Name: "TEST_MULTIPLE_LINES", - RepositoryPattern: "*/*", - Value: `Hello -World`, - }) - - nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, "github.com/gitpod-io/empty", username, api) - if err != nil { - t.Fatal(err) - } - defer func() { - sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer scancel() - - sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer sapi.Done(t) - - stopWs(true, sapi) - }() - - wsUrl, err := url.Parse(nfo.LatestInstance.IdeURL) - if err != nil { - t.Fatal(err) - } - - wId := nfo.Workspace.ID - ownerToken, err := server.GetOwnerToken(ctx, wId) - if err != nil { - t.Fatal(err) - } - - urlComponents := strings.Split(wsUrl.Host, ".") - connUrl := []string{urlComponents[0], "ssh"} - connUrl = append(connUrl, urlComponents[1:]...) - connUrlStr := strings.Join(connUrl, ".") - - cli, err := sshclient.DialWithPasswd(connUrlStr+":22", wId, ownerToken) - if err != nil { - t.Fatal(err) - } - - output, err := cli.Cmd("gp info").Output() - if err != nil && err != io.EOF { - log.Println("[error]", err) - time.Sleep(1 * time.Second) - } - - t.Log(string(output)) - - return testCtx - }). - Feature() - - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } diff --git a/test/tests/ide/vscode/python_ws_test.go b/test/tests/ide/vscode/python_ws_test.go index c52637b46e87d5..d863867c3a18aa 100644 --- a/test/tests/ide/vscode/python_ws_test.go +++ b/test/tests/ide/vscode/python_ws_test.go @@ -4,180 +4,8 @@ package ide -import ( - "context" - "crypto/sha256" - "encoding/base64" - "errors" - "fmt" - "os" - "testing" - "time" - - "sigs.k8s.io/e2e-framework/pkg/envconf" - "sigs.k8s.io/e2e-framework/pkg/features" - - protocol "github.com/gitpod-io/gitpod/gitpod-protocol" - agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" - "github.com/gitpod-io/gitpod/test/pkg/integration" -) - -func poolTask(task func() (bool, error)) (bool, error) { - timeout := time.After(10 * time.Minute) - ticker := time.Tick(20 * time.Second) - for { - select { - case <-timeout: - return false, errors.New("timed out") - case <-ticker: - ok, err := task() - if err != nil { - return false, err - } else if ok { - return true, nil - } - } - } -} +import "testing" func TestPythonExtWorkspace(t *testing.T) { - userToken, _ := os.LookupEnv("USER_TOKEN") - integration.SkipWithoutUsername(t, username) - integration.SkipWithoutUserToken(t, userToken) - - f := features.New("PythonExtensionWorkspace"). - WithLabel("component", "server"). - Assess("it can run python extension in a workspace", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - ctx, cancel := context.WithTimeout(testCtx, 5*time.Minute) - defer cancel() - - api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) - t.Cleanup(func() { - api.Done(t) - }) - - userId, err := api.CreateUser(username, userToken) - if err != nil { - t.Fatal(err) - } - - serverOpts := []integration.GitpodServerOpt{integration.WithGitpodUser(username)} - server, err := api.GitpodServer(serverOpts...) - if err != nil { - t.Fatal(err) - } - - _, err = server.UpdateLoggedInUser(ctx, &protocol.User{ - AdditionalData: &protocol.AdditionalUserData{ - IdeSettings: &protocol.IDESettings{ - DefaultIde: "code-latest", - }, - }, - }) - if err != nil { - t.Fatalf("cannot set ide to vscode insiders: %q", err) - } - - nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, "github.com/gitpod-io/python-test-workspace", username, api) - if err != nil { - t.Fatal(err) - } - defer func() { - sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer scancel() - - sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer sapi.Done(t) - - stopWs(true, sapi) - }() - - _, err = integration.WaitForWorkspaceStart(t, ctx, nfo.LatestInstance.ID, nfo.Workspace.ID, api) - if err != nil { - t.Fatal(err) - } - - serverConfig, err := integration.GetServerConfig(cfg.Namespace(), cfg.Client()) - if err != nil { - t.Fatal(err) - } - - hash := sha256.Sum256([]byte(userId + serverConfig.Session.Secret)) - secretKey, err := api.CreateGitpodOneTimeSecret(fmt.Sprintf("%x", hash)) - if err != nil { - t.Fatal(err) - } - - sessionCookie, err := api.GitpodSessionCookie(userId, secretKey) - if err != nil { - t.Fatal(err) - } - - rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), integration.WithInstanceID(nfo.LatestInstance.ID), integration.WithWorkspacekitLift(true)) - if err != nil { - t.Fatal(err) - } - defer rsa.Close() - integration.DeferCloser(t, closer) - - _, err = poolTask(func() (bool, error) { - var resp agent.ExecResponse - err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ - Dir: "/workspace/python-test-workspace", - Command: "test", - Args: []string{ - "-f", - "__init_task_done__", - }, - }, &resp) - - return resp.ExitCode == 0, nil - }) - if err != nil { - t.Fatal(err) - } - - serverUrl, err := api.GetServerEndpoint() - - jsonCookie := fmt.Sprintf( - `{"name": "%v","value": "%v", "url": "%v","expires": %v,"httpOnly": %v,"secure": %v,"sameSite": "Lax"}`, - sessionCookie.Name, - sessionCookie.Value, - serverUrl, - sessionCookie.Expires.Unix(), - sessionCookie.HttpOnly, - sessionCookie.Secure, - ) - - var resp agent.ExecResponse - err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ - Dir: "/workspace/python-test-workspace", - Command: "yarn", - Args: []string{ - "gp-code-server-test", - fmt.Sprintf("--endpoint=%s", nfo.LatestInstance.IdeURL), - "--workspacePath=./src/testWorkspace", - "--extensionDevelopmentPath=./out", - "--extensionTestsPath=./out/test/suite", - }, - Env: []string{ - fmt.Sprintf("AUTH_COOKIE=%s", base64.StdEncoding.EncodeToString([]byte(jsonCookie))), - }, - }, &resp) - - if err != nil { - t.Fatal(err) - } - - t.Log("Ide integration stdout:\n", resp.Stdout) - if resp.ExitCode != 0 { - t.Log("Ide integration stderr:\n", resp.Stderr) - t.Fatal("There was an error running ide test") - } - - return testCtx - }). - Feature() - - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } diff --git a/test/tests/smoke-test/smoke_test.go b/test/tests/smoke-test/smoke_test.go index 3d8138eda9c25d..e8e0b63949cfb6 100644 --- a/test/tests/smoke-test/smoke_test.go +++ b/test/tests/smoke-test/smoke_test.go @@ -4,53 +4,8 @@ package smoketest -import ( - "context" - "os" - "testing" - "time" - - "sigs.k8s.io/e2e-framework/pkg/envconf" - "sigs.k8s.io/e2e-framework/pkg/features" - - "github.com/gitpod-io/gitpod/test/pkg/integration" -) +import "testing" func TestStartWorkspaceWithImageBuild(t *testing.T) { - userToken, _ := os.LookupEnv("USER_TOKEN") - integration.SkipWithoutUsername(t, username) - integration.SkipWithoutUserToken(t, userToken) - - f := features.New("Start regular workspace"). - Assess("it can start a regular workspace with image build", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - ctx, cancel := context.WithTimeout(testCtx, 5*time.Minute) - defer cancel() - - api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) - t.Cleanup(func() { - api.Done(t) - }) - - _, err := api.CreateUser(username, userToken) - if err != nil { - t.Fatal(err) - } - - _, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, "imagebuild/https://github.com/gitpod-integration-test/example", username, api) - if err != nil { - t.Fatal(err) - } - defer func() { - sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer scancel() - - sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer sapi.Done(t) - - _, _ = stopWs(true, sapi) - }() - return testCtx - }). - Feature() - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } diff --git a/test/tests/workspace/contexts_test.go b/test/tests/workspace/contexts_test.go index d37f362b369280..b9e8dafd4d4b43 100644 --- a/test/tests/workspace/contexts_test.go +++ b/test/tests/workspace/contexts_test.go @@ -4,193 +4,12 @@ package workspace -import ( - "context" - "fmt" - "os" - "testing" - "time" - - "sigs.k8s.io/e2e-framework/pkg/envconf" - "sigs.k8s.io/e2e-framework/pkg/features" - - "github.com/gitpod-io/gitpod/test/pkg/integration" - "github.com/gitpod-io/gitpod/test/pkg/report" -) - -type ContextTest struct { - Skip bool - Name string - ContextURL string - WorkspaceRoot string - ExpectedBranch string - ExpectedBranchFunc func(username string) string - IgnoreError bool -} +import "testing" func TestGitHubContexts(t *testing.T) { - tests := []ContextTest{ - { - Name: "open repository", - ContextURL: "github.com/gitpod-io/template-golang-cli", - WorkspaceRoot: "/workspace/template-golang-cli", - ExpectedBranch: "main", - }, - { - Name: "open branch", - ContextURL: "github.com/gitpod-io/gitpod-test-repo/tree/integration-test-1", - WorkspaceRoot: "/workspace/gitpod-test-repo", - ExpectedBranch: "integration-test-1", - }, - { - // Branch name decisions are not tested in the workspace as it is the server side logic - Name: "open issue", - ContextURL: "github.com/gitpod-io/gitpod-test-repo/issues/88", - WorkspaceRoot: "/workspace/gitpod-test-repo", - }, - { - Name: "open tag", - ContextURL: "github.com/gitpod-io/gitpod-test-repo/tree/integration-test-context-tag", - WorkspaceRoot: "/workspace/gitpod-test-repo", - ExpectedBranch: "HEAD", - }, - { - Name: "Git LFS support", - ContextURL: "github.com/atduarte/lfs-test", - WorkspaceRoot: "/workspace/lfs-test", - ExpectedBranch: "main", - }, - { - Name: "empty repo", - ContextURL: "github.com/gitpod-io/empty", - WorkspaceRoot: "/workspace/empty", - ExpectedBranch: "HEAD", - IgnoreError: true, - }, - } - runContextTests(t, tests) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } func TestGitLabContexts(t *testing.T) { - if !gitlab { - t.Skip("Skipping gitlab integration tests") - } - tests := []ContextTest{ - { - Name: "open repository", - ContextURL: "gitlab.com/AlexTugarev/gp-test", - WorkspaceRoot: "/workspace/gp-test", - ExpectedBranch: "master", - }, - { - Name: "open branch", - ContextURL: "gitlab.com/AlexTugarev/gp-test/tree/wip", - WorkspaceRoot: "/workspace/gp-test", - ExpectedBranch: "wip", - }, - { - Name: "open issue", - ContextURL: "gitlab.com/AlexTugarev/gp-test/issues/1", - WorkspaceRoot: "/workspace/gp-test", - }, - { - Name: "open tag", - ContextURL: "gitlab.com/AlexTugarev/gp-test/merge_requests/2", - WorkspaceRoot: "/workspace/gp-test", - ExpectedBranch: "wip2", - }, - } - runContextTests(t, tests) -} - -func runContextTests(t *testing.T, tests []ContextTest) { - userToken, _ := os.LookupEnv("USER_TOKEN") - integration.SkipWithoutUsername(t, username) - integration.SkipWithoutUserToken(t, userToken) - - f := features.New("context"). - WithLabel("component", "server"). - Assess("should run context tests", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - - sctx, scancel := context.WithTimeout(testCtx, time.Duration(10*len(tests))*time.Minute) - defer scancel() - - api := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer api.Done(t) - - for _, test := range tests { - test := test - t.Run(test.ContextURL, func(t *testing.T) { - report.SetupReport(t, report.FeatureContentInit, fmt.Sprintf("Test to open %v", test.ContextURL)) - if test.Skip { - t.SkipNow() - } - - t.Parallel() - - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(5*len(tests))*time.Minute) - defer cancel() - - api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer api.Done(t) - - _, err := api.CreateUser(username, userToken) - if err != nil { - t.Fatal(err) - } - - nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, test.ContextURL, username, api) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - sctx, scancel := context.WithTimeout(context.Background(), 10*time.Minute) - defer scancel() - - sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer sapi.Done(t) - - _, err := stopWs(true, sapi) - if err != nil { - t.Fatal(err) - } - }) - - rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), integration.WithInstanceID(nfo.LatestInstance.ID)) - if err != nil { - t.Fatal(err) - } - defer rsa.Close() - integration.DeferCloser(t, closer) - - if test.ExpectedBranch == "" && test.ExpectedBranchFunc == nil { - return - } - - // get actual from workspace - git := integration.Git(rsa) - err = git.ConfigSafeDirectory() - if err != nil { - t.Fatal(err) - } - actBranch, err := git.GetBranch(test.WorkspaceRoot, test.IgnoreError) - if err != nil { - t.Fatal(err) - } - - expectedBranch := test.ExpectedBranch - if test.ExpectedBranchFunc != nil { - expectedBranch = test.ExpectedBranchFunc(username) - } - if actBranch != expectedBranch { - t.Fatalf("expected branch '%s', got '%s'!", expectedBranch, actBranch) - } - }) - } - return testCtx - }). - Feature() - - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } diff --git a/test/tests/workspace/disk_test.go b/test/tests/workspace/disk_test.go index f14e327c7bee30..ab7386031e0dc6 100644 --- a/test/tests/workspace/disk_test.go +++ b/test/tests/workspace/disk_test.go @@ -4,113 +4,8 @@ package workspace -import ( - "context" - "fmt" - "strings" - - "testing" - "time" - - "sigs.k8s.io/e2e-framework/pkg/envconf" - "sigs.k8s.io/e2e-framework/pkg/features" - - "github.com/gitpod-io/gitpod/test/pkg/integration" - "github.com/gitpod-io/gitpod/test/pkg/report" -) - -type DiskTest struct { - Name string - ContextURL string - SpaceToAllocate string - TestFilePath string - ExpectError bool -} +import "testing" func TestDiskActions(t *testing.T) { - tests := []DiskTest{ - { - Name: "xfs-quota-is_exceeded", - ContextURL: "github.com/gitpod-io/empty", - SpaceToAllocate: "55G", - TestFilePath: "/workspace/is-exceeded", - ExpectError: true, - }, - { - Name: "xfs-quota-is_OK", - ContextURL: "github.com/gitpod-io/empty", - SpaceToAllocate: "4G", - TestFilePath: "/workspace/is-OK", - ExpectError: false, - }, - } - runDiskTests(t, tests) -} - -func runDiskTests(t *testing.T, tests []DiskTest) { - f := features.New("ResourceLimiting"). - WithLabel("component", "workspace"). - Assess("it can enforce disk limits", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - - ctx, cancel := context.WithTimeout(testCtx, time.Duration(5*len(tests))*time.Minute) - defer cancel() - - api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer api.Done(t) - - for _, test := range tests { - test := test - t.Run(test.Name, func(t *testing.T) { - report.SetupReport(t, report.FeatureResourceLimit, fmt.Sprintf("Test to open %v", test.ContextURL)) - - t.Parallel() - - nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, test.ContextURL, username, api) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - sctx, scancel := context.WithTimeout(context.Background(), 10*time.Minute) - scancel() - - sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) - sapi.Done(t) - _, err := stopWs(false, sapi) - if err != nil { - t.Fatal(err) - } - }) - rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), - kubeconfig, cfg.Client(), - integration.WithInstanceID(nfo.LatestInstance.ID), - ) - if err != nil { - t.Fatal(err) - } - defer rsa.Close() - integration.DeferCloser(t, closer) - diskClient := integration.Disk(rsa) - - err = diskClient.Fallocate(test.TestFilePath, test.SpaceToAllocate) - - if test.ExpectError { - if err != nil && strings.Contains(err.Error(), integration.NoSpaceErrorMsg) { - // NOM - } else { - t.Fatalf("expected an error object containing %s, got '%v'!", integration.NoSpaceErrorMsg, err) - } - } else { - if err != nil { - t.Fatal(err) - } - } - t.Log("test finished successfully") - }) - } - return testCtx - }). - Feature() - - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } diff --git a/test/tests/workspace/example_test.go b/test/tests/workspace/example_test.go index 39d8b9c562fa5a..a85119eeab0aeb 100644 --- a/test/tests/workspace/example_test.go +++ b/test/tests/workspace/example_test.go @@ -6,100 +6,17 @@ package workspace import ( "context" - "os" "testing" "time" "sigs.k8s.io/e2e-framework/pkg/envconf" "sigs.k8s.io/e2e-framework/pkg/features" - agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" "github.com/gitpod-io/gitpod/test/pkg/integration" - "github.com/gitpod-io/gitpod/test/pkg/report" ) func TestWorkspaceInstrumentation(t *testing.T) { - userToken, _ := os.LookupEnv("USER_TOKEN") - integration.SkipWithoutUsername(t, username) - integration.SkipWithoutUserToken(t, userToken) - tests := []struct { - Name string - ContextURL string - WorkspaceRoot string - }{ - { - Name: "example", - ContextURL: "https://github.com/gitpod-io/empty", - WorkspaceRoot: "/workspace/empty", - }, - } - - f := features.New("instrumentation"). - WithLabel("component", "server"). - Assess("it can instrument a workspace", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - for _, test := range tests { - test := test - t.Run(test.ContextURL, func(t *testing.T) { - report.SetupReport(t, report.FeatureExample, "this is the example test for instrumenting a workspace") - - t.Parallel() - - ctx, cancel := context.WithTimeout(testCtx, time.Duration(5*len(tests))*time.Minute) - defer cancel() - - api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) - t.Cleanup(func() { - api.Done(t) - }) - - _, err := api.CreateUser(username, userToken) - if err != nil { - t.Fatal(err) - } - - nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, test.ContextURL, username, api) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer scancel() - - sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer sapi.Done(t) - - _, err := stopWs(true, sapi) - if err != nil { - t.Fatal(err) - } - }) - - rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), integration.WithInstanceID(nfo.LatestInstance.ID)) - if err != nil { - t.Fatal(err) - } - defer rsa.Close() - integration.DeferCloser(t, closer) - - var ls agent.ListDirResponse - err = rsa.Call("WorkspaceAgent.ListDir", &agent.ListDirRequest{ - Dir: test.WorkspaceRoot, - }, &ls) - if err != nil { - t.Fatal(err) - } - for _, f := range ls.Files { - t.Log(f) - } - }) - } - - return testCtx - }). - Feature() - - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } func TestLaunchWorkspaceDirectly(t *testing.T) { diff --git a/test/tests/workspace/git_hooks_test.go b/test/tests/workspace/git_hooks_test.go index 931821b9c90532..4b7cd8f047d069 100644 --- a/test/tests/workspace/git_hooks_test.go +++ b/test/tests/workspace/git_hooks_test.go @@ -4,127 +4,8 @@ package workspace -import ( - "context" - "os" - "testing" - "time" - - "sigs.k8s.io/e2e-framework/pkg/envconf" - "sigs.k8s.io/e2e-framework/pkg/features" - - agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" - "github.com/gitpod-io/gitpod/test/pkg/integration" -) - -const ( - FILE_CREATED_HOOKS = "output.txt" -) - -type GitHooksTestCase struct { - Name string - ContextURL string - WorkspaceRoot string -} +import "testing" func TestGitHooks(t *testing.T) { - userToken, _ := os.LookupEnv("USER_TOKEN") - integration.SkipWithoutUsername(t, username) - integration.SkipWithoutUserToken(t, userToken) - - tests := []GitHooksTestCase{ - { - Name: "husky", - ContextURL: "https://github.com/gitpod-io/gitpod-test-repo/tree/husky", - WorkspaceRoot: "/workspace/gitpod-test-repo", - }, - } - - f := features.New("git hooks"). - WithLabel("component", "server"). - Assess("should run git hooks tests", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - ffs := []struct { - Name string - FF string - }{ - {Name: "classic"}, - } - - for _, ff := range ffs { - func() { - ctx, cancel := context.WithTimeout(testCtx, 10*time.Minute) - defer cancel() - - api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer api.Done(t) - - username := username + ff.Name - userId, err := api.CreateUser(username, userToken) - if err != nil { - t.Fatal(err) - } - - if err := api.UpdateUserFeatureFlag(userId, ff.FF); err != nil { - t.Fatal(err) - } - }() - } - - for _, ff := range ffs { - for _, test := range tests { - test := test - t.Run(test.ContextURL+"_"+ff.Name, func(t *testing.T) { - t.Parallel() - - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(5*len(tests)*len(ffs))*time.Minute) - defer cancel() - - username := username + ff.Name - - api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer api.Done(t) - - wsInfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, test.ContextURL, username, api) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - sctx, scancel := context.WithTimeout(context.Background(), 10*time.Minute) - defer scancel() - - sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer sapi.Done(t) - - if _, err := stopWs(true, sapi); err != nil { - t.Fatal(err) - } - }) - rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), integration.WithInstanceID(wsInfo.LatestInstance.ID)) - if err != nil { - t.Fatal(err) - } - defer rsa.Close() - integration.DeferCloser(t, closer) - - var ls agent.ListDirResponse - err = rsa.Call("WorkspaceAgent.ListDir", &agent.ListDirRequest{ - Dir: test.WorkspaceRoot, - }, &ls) - if err != nil { - t.Fatal(err) - } - for _, f := range ls.Files { - if f == FILE_CREATED_HOOKS { - t.Fatal("Checkout hooks are executed") - } - } - }) - } - } - return testCtx - }). - Feature() - - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } diff --git a/test/tests/workspace/git_test.go b/test/tests/workspace/git_test.go index e7c4695c66cdf5..63cbb48975b996 100644 --- a/test/tests/workspace/git_test.go +++ b/test/tests/workspace/git_test.go @@ -4,204 +4,8 @@ package workspace -import ( - "context" - "fmt" - "os" - "testing" - "time" - - "sigs.k8s.io/e2e-framework/pkg/envconf" - "sigs.k8s.io/e2e-framework/pkg/features" - - agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" - "github.com/gitpod-io/gitpod/test/pkg/integration" -) - -type GitTest struct { - Skip bool - Name string - ContextURL string - WorkspaceRoot string - Action GitFunc -} - -type GitFunc func(rsa *integration.RpcClient, git integration.GitClient, workspaceRoot string) error +import "testing" func TestGitActions(t *testing.T) { - userToken, _ := os.LookupEnv("USER_TOKEN") - integration.SkipWithoutUsername(t, username) - integration.SkipWithoutUserToken(t, userToken) - - tests := []GitTest{ - { - Name: "create, add and commit", - ContextURL: "github.com/gitpod-io/gitpod-test-repo/tree/integration-test/commit", - WorkspaceRoot: "/workspace/gitpod-test-repo", - Action: func(rsa *integration.RpcClient, git integration.GitClient, workspaceRoot string) (err error) { - var resp agent.ExecResponse - err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ - Dir: workspaceRoot, - Command: "bash", - Args: []string{ - "-c", - "touch file_to_commit.txt", - }, - }, &resp) - if err != nil { - return err - } - if resp.ExitCode != 0 { - return fmt.Errorf("file create returned rc: %d, out: %v, err: %v", resp.ExitCode, resp.Stdout, resp.Stderr) - } - err = git.ConfigSafeDirectory() - if err != nil { - return err - } - err = git.ConfigUserName(workspaceRoot, username) - if err != nil { - return err - } - err = git.ConfigUserEmail(workspaceRoot) - if err != nil { - return err - } - err = git.Add(workspaceRoot) - if err != nil { - return err - } - err = git.Commit(workspaceRoot, "automatic test commit", false, "--allow-empty") - if err != nil { - return err - } - return nil - }, - }, - { - // as of Apr 14, 2023, test fails with: - // fatal: could not read Username for 'https://github.com': No such device or address - Skip: true, - Name: "create, add and commit and PUSH", - ContextURL: "github.com/gitpod-io/gitpod-test-repo/tree/integration-test/commit-and-push", - WorkspaceRoot: "/workspace/gitpod-test-repo", - Action: func(rsa *integration.RpcClient, git integration.GitClient, workspaceRoot string) (err error) { - var resp agent.ExecResponse - err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ - Dir: workspaceRoot, - Command: "bash", - Args: []string{ - "-c", - "touch file_to_commit.txt", - }, - }, &resp) - if err != nil { - return err - } - if resp.ExitCode != 0 { - return fmt.Errorf("file create returned rc: %d, out: %v, err: %v", resp.ExitCode, resp.Stdout, resp.Stderr) - } - err = git.ConfigSafeDirectory() - if err != nil { - return err - } - err = git.ConfigUserName(workspaceRoot, username) - if err != nil { - return err - } - err = git.ConfigUserEmail(workspaceRoot) - if err != nil { - return err - } - err = git.Add(workspaceRoot) - if err != nil { - return err - } - err = git.Commit(workspaceRoot, "automatic test commit", false, "--allow-empty") - if err != nil { - return err - } - err = git.Push(workspaceRoot, false) - if err != nil { - return err - } - return nil - }, - }, - } - - f := features.New("GitActions"). - WithLabel("component", "workspace"). - Assess("it can run git actions", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - ctx, cancel := context.WithTimeout(testCtx, time.Duration(5*len(tests))*time.Minute) - defer cancel() - - api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) - t.Cleanup(func() { - api.Done(t) - }) - - ffs := []struct { - Name string - FF string - }{ - {Name: "classic"}, - } - - for _, ff := range ffs { - for _, test := range tests { - test := test - t.Run(test.ContextURL+"_"+ff.Name, func(t *testing.T) { - t.Parallel() - if test.Skip { - t.SkipNow() - } - - username := username + ff.Name - userId, err := api.CreateUser(username, userToken) - if err != nil { - t.Fatal(err) - } - - if err := api.UpdateUserFeatureFlag(userId, ff.FF); err != nil { - t.Fatal(err) - } - - nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, test.ContextURL, username, api) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { - sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer scancel() - - sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer sapi.Done(t) - - _, err := stopWs(false, sapi) - if err != nil { - t.Fatal(err) - } - }) - - rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), integration.WithInstanceID(nfo.LatestInstance.ID)) - if err != nil { - t.Fatal(err) - } - defer rsa.Close() - integration.DeferCloser(t, closer) - - git := integration.Git(rsa) - err = test.Action(rsa, git, test.WorkspaceRoot) - if err != nil { - t.Fatal(err) - } - t.Log("test finished successfully") - }) - } - } - return testCtx - }). - Feature() - - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } diff --git a/test/tests/workspace/ports_test.go b/test/tests/workspace/ports_test.go index a4c4e52c8eff12..1b572a13be216b 100644 --- a/test/tests/workspace/ports_test.go +++ b/test/tests/workspace/ports_test.go @@ -4,213 +4,8 @@ package workspace -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "os" - "reflect" - "strings" - "testing" - "time" - - gitpod "github.com/gitpod-io/gitpod/gitpod-protocol" - agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" - "github.com/gitpod-io/gitpod/test/pkg/integration" - "sigs.k8s.io/e2e-framework/pkg/envconf" - "sigs.k8s.io/e2e-framework/pkg/features" -) +import "testing" func TestRegularWorkspacePorts(t *testing.T) { - userToken, _ := os.LookupEnv("USER_TOKEN") - integration.SkipWithoutUsername(t, username) - integration.SkipWithoutUserToken(t, userToken) - - // This branch exposes a python server on port 3000 as part of the Gitpod tasks. - testRepo := "https://github.com/gitpod-io/gitpod-test-repo/tree/integration-test/ports" - testRepoName := "gitpod-test-repo" - wsLoc := fmt.Sprintf("/workspace/%s", testRepoName) - - f := features.New("ports"). - WithLabel("component", "workspace"). - WithLabel("type", "ports"). - Assess("it can open and access workspace ports", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - t.Parallel() - - ctx, cancel := context.WithTimeout(testCtx, time.Duration(5*time.Minute)) - defer cancel() - - api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) - t.Cleanup(func() { - api.Done(t) - }) - - _, err := api.CreateUser(username, userToken) - if err != nil { - t.Fatal(err) - } - - serverOpts := []integration.GitpodServerOpt{integration.WithGitpodUser(username)} - server, err := api.GitpodServer(serverOpts...) - if err != nil { - t.Fatal(err) - } - - // Must change supervisor address from localhost to 10.0.5.2. - err = server.SetEnvVar(ctx, &gitpod.UserEnvVarValue{ - Name: "SUPERVISOR_ADDR", - Value: `10.0.5.2:22999`, - RepositoryPattern: "gitpod-io/" + testRepoName, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - err := server.DeleteEnvVar(ctx, &gitpod.UserEnvVarValue{ - Name: "SUPERVISOR_ADDR", - RepositoryPattern: "gitpod-io/" + testRepoName, - }) - if err != nil { - t.Fatal(err) - } - }() - - nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, testRepo, username, api, integration.WithGitpodUser(username)) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer scancel() - - sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer sapi.Done(t) - - if _, err = stopWs(true, sapi); err != nil { - t.Errorf("cannot stop workspace: %q", err) - } - }) - - instanceId := nfo.LatestInstance.ID - rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), integration.WithInstanceID(instanceId)) - integration.DeferCloser(t, closer) - if err != nil { - t.Fatalf("unexpected error instrumenting workspace: %v", err) - } - defer rsa.Close() - - type Port struct { - LocalPort int `json:"localPort,omitempty"` - Exposed struct { - Visibility string `json:"visibility,omitempty"` - Url string `json:"url,omitempty"` - } `json:"exposed,omitempty"` - } - var portsResp struct { - Result struct { - Ports []*Port `json:"ports,omitempty"` - } `json:"result"` - } - - expectPort := func(expected Port) { - for i := 0; i < 10; i++ { - var res agent.ExecResponse - err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ - Dir: wsLoc, - Command: "curl", - // nftable rule only forwards to this ip address - Args: []string{"10.0.5.2:22999/_supervisor/v1/status/ports"}, - }, &res) - if err != nil { - t.Fatal(err) - } - err = json.Unmarshal([]byte(res.Stdout), &portsResp) - if err != nil { - t.Fatalf("cannot decode supervisor ports status response: %s", err) - } - - t.Logf("ports: %s (%d)", res.Stdout, res.ExitCode) - if len(portsResp.Result.Ports) != 1 { - t.Logf("expected one port to be open, but got %d, retrying", len(portsResp.Result.Ports)) - time.Sleep(2 * time.Second) - continue - } - if !reflect.DeepEqual(expected, *portsResp.Result.Ports[0]) { - t.Logf("expected %v but got %v, retrying", expected, *portsResp.Result.Ports[0]) - time.Sleep(2 * time.Second) - continue - } - - // Got expected port. - return - } - - // If we get here, we didn't get the expected port status after retrying. - t.Fatalf("did not get expected port status after 10 attempts") - } - - t.Logf("checking that port has been auto-detected, and is private") - portUrl := fmt.Sprintf("https://%d-%s", 3000, strings.TrimPrefix(nfo.LatestInstance.IdeURL, "https://")) - expectPort(Port{ - LocalPort: 3000, - Exposed: struct { - Visibility string `json:"visibility,omitempty"` - Url string `json:"url,omitempty"` - }{ - Visibility: "private", - Url: portUrl, - }, - }) - - t.Logf("checking that private port is not accessible from outside the workspace at %s", portUrl) - res, err := http.Get(portUrl) - if err != nil { - t.Fatal(err) - } - if res.StatusCode != http.StatusUnauthorized { - t.Fatalf("expected status code 401, but got %d", res.StatusCode) - } - - // Make port public. - t.Logf("making port public via gp ports visibility") - var res1 agent.ExecResponse - err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ - Dir: wsLoc, - Command: "gp", - // nftable rule only forwards to this ip address - Args: []string{"ports", "visibility", "3000:public"}, - }, &res1) - if err != nil { - t.Fatal(err) - } - t.Logf("debug: gp CLI output: %s, %s, %d", res1.Stdout, res1.Stderr, res1.ExitCode) - - t.Logf("checking that port is now public") - expectPort(Port{ - LocalPort: 3000, - Exposed: struct { - Visibility string `json:"visibility,omitempty"` - Url string `json:"url,omitempty"` - }{ - Visibility: "public", - Url: portUrl, - }, - }) - - t.Logf("checking if port is accessible from outside the workspace at %s", portUrl) - res, err = http.Get(portUrl) - if err != nil { - t.Fatal(err) - } - if res.StatusCode != http.StatusOK { - t.Fatalf("expected status code 200, but got %d", res.StatusCode) - } - - return testCtx - }). - Feature() - - testEnv.Test(t, f) + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } diff --git a/test/tests/workspace/process_priority_test.go b/test/tests/workspace/process_priority_test.go index 7e00da25f52975..a44065a750fd34 100644 --- a/test/tests/workspace/process_priority_test.go +++ b/test/tests/workspace/process_priority_test.go @@ -4,140 +4,8 @@ package workspace -import ( - "context" - "fmt" - "os" - "strconv" - "strings" - "testing" - "time" - - agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" - "github.com/gitpod-io/gitpod/test/pkg/integration" - "sigs.k8s.io/e2e-framework/pkg/envconf" - "sigs.k8s.io/e2e-framework/pkg/features" -) +import "testing" func TestProcessPriority(t *testing.T) { - userToken, _ := os.LookupEnv("USER_TOKEN") - integration.SkipWithoutUsername(t, username) - integration.SkipWithoutUserToken(t, userToken) - - f := features.New("process priority"). - WithLabel("component", "workspace"). - WithLabel("type", "process priority"). - Assess("it has set process priority", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - t.Parallel() - - ctx, cancel := context.WithTimeout(testCtx, time.Duration(5*time.Minute)) - defer cancel() - - api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) - t.Cleanup(func() { - api.Done(t) - }) - - _, err := api.CreateUser(username, userToken) - if err != nil { - t.Fatal(err) - } - - nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, "https://github.com/gitpod-io/empty", username, api, integration.WithGitpodUser(username)) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer scancel() - - sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) - defer sapi.Done(t) - - if _, err = stopWs(true, sapi); err != nil { - t.Errorf("cannot stop workspace: %v", err) - } - }) - - rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), integration.WithInstanceID(nfo.LatestInstance.ID)) - integration.DeferCloser(t, closer) - if err != nil { - t.Fatalf("unexpected error instrumenting workspace: %v", err) - } - defer rsa.Close() - - t.Logf("waiting for the next ws-daemon tick, before running ps") - time.Sleep(15 * time.Second) - - var res agent.ExecResponse - err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ - Dir: "/workspace", - Command: "ps", - Args: []string{"eax", "-o", "ni,cmd", "--no-headers"}, - }, &res) - if err != nil { - t.Fatal(err) - } - if res.ExitCode != 0 { - t.Fatalf("ps failed (%d): %s", res.ExitCode, res.Stderr) - } - - checkProcessPriorities(t, res.Stdout) - - return testCtx - }). - Feature() - - testEnv.Test(t, f) -} - -func checkProcessPriorities(t *testing.T, output string) { - t.Helper() - - processes := strings.Split(output, "\n") - for _, p := range processes { - parts := strings.Fields(p) - if len(parts) >= 2 { - checkProcessPriority(t, parts[0], parts[1]) - } - } -} - -func checkProcessPriority(t *testing.T, priority, process string) { - t.Helper() - - actualPrio, err := strconv.Atoi(priority) - if err != nil { - return - } - - expectedPrio, err := determinePriority(process) - if err != nil { - return - } - - if actualPrio != expectedPrio { - t.Fatalf("expected priority of %v for process %v, but was %v", expectedPrio, process, actualPrio) - } -} - -func determinePriority(process string) (int, error) { - if strings.HasSuffix(process, "supervisor") { - return -10, nil - } - - if strings.HasSuffix(process, "/bin/code-server") { - return -10, nil - } - - if strings.HasSuffix(process, "/ide/bin/gitpod-code") { - return -10, nil - } - - if strings.HasSuffix(process, "/ide/node") { - return -5, nil - } - - return 0, fmt.Errorf("unknown") + t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") } From fb222ddb88a1717f432bf8daa747a310ab633924 Mon Sep 17 00:00:00 2001 From: Kyle Brennan Date: Thu, 1 Oct 2026 17:25:12 +0000 Subject: [PATCH 2/4] Keep the workspace workflow dedicated to workspace tests Co-authored-by: Codex --- .../workflows/workspace-integration-tests.yml | 10 +------- test/README.md | 23 +++++++++++-------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/.github/workflows/workspace-integration-tests.yml b/.github/workflows/workspace-integration-tests.yml index db6e6b6cc5f10b..5415b1869bf389 100644 --- a/.github/workflows/workspace-integration-tests.yml +++ b/.github/workflows/workspace-integration-tests.yml @@ -5,14 +5,6 @@ permissions: on: workflow_dispatch: inputs: - test_suite: - required: false - type: choice - description: "Integration suite with retained functional coverage" - options: - - workspace - - webapp - default: workspace name: required: false type: string @@ -176,7 +168,7 @@ jobs: uses: ./.github/actions/integration-tests with: preview_name: ${{ needs.configuration.outputs.name }} - test_suite: ${{ inputs.test_suite || 'workspace' }} + test_suite: workspace notify_slack_webhook: ${{ secrets.WORKSPACE_SLACK_WEBHOOK }} github_token: ${{ secrets.GITHUB_TOKEN }} identity_provider: ${{ secrets.DEV_PREVIEW_PROVIDER }} diff --git a/test/README.md b/test/README.md index 0d862c9da9c5fa..337cb8260f5dd5 100644 --- a/test/README.md +++ b/test/README.md @@ -22,20 +22,23 @@ Such tests are for example: ## Automatically at Gitpod -You can opt-in to run the integrations tests as part of the build job. that runs the integration tests against preview environments. +The GitHub Actions **Branch Build** workflow can run integration tests against a +preview environment. To run the server and database (`webapp`) suite, select this +option in the PR description: - > Retained tests use builtin or temporary users by default. An explicitly selected - > `username` must already exist in the preview database. +```markdown +- [x] with-integration-tests=webapp +``` -Example command: +This enables a preview with a large VM, builds and deploys the branch, and runs +`./test/run.sh -s webapp` through the shared integration-test action. The option +also accepts `all` to include webapp with the other suites. -```console -werft job run github -a with-preview=true -a with-integration-tests=webapp -f -``` +The **Workspace integration tests** workflow always runs `workspace`. There is +no separate scheduled webapp workflow. -The GitHub Actions **Workspace integration tests** workflow also supports manual -runs of either `workspace` or `webapp` via its `test_suite` input. Scheduled runs -and reusable workflow calls continue to run `workspace`. +> Retained tests use builtin or temporary users by default. An explicitly selected +> `username` must already exist in the preview database. ## Manually From 35cc9c67b74855087977d87cd78126e525ed3376 Mon Sep 17 00:00:00 2001 From: Kyle Brennan Date: Thu, 1 Oct 2026 17:33:47 +0000 Subject: [PATCH 3/4] Preserve manual integration tests while omitting CI credentials Co-authored-by: Codex --- .github/actions/integration-tests/action.yml | 4 +- .github/workflows/ide-integration-tests.yml | 6 +- .../preview-env-check-regressions.yml | 4 +- dev/jetbrains-test/README.md | 37 +- test/BUILD.yaml | 32 +- test/README.md | 145 ++---- test/pkg/integration/apis.go | 110 +++++ test/pkg/integration/setup.go | 6 + test/run.sh | 6 - .../ws-daemon/network_limiting_test.go | 97 ++++- .../components/ws-manager/dotfiles_test.go | 185 +++++++- .../ide/jetbrains/base_in_workspace_test.go | 49 +++ test/tests/ide/jetbrains/base_test.go | 412 ++++++++++++++++++ test/tests/ide/jetbrains/gateway_test.go | 267 +++++++++++- test/tests/ide/jetbrains/warmup-indexing.sh | 38 ++ test/tests/ide/ssh/ssh_gateway_test.go | 109 ++++- test/tests/ide/vscode/python_ws_test.go | 176 +++++++- test/tests/smoke-test/smoke_test.go | 49 ++- test/tests/workspace/contexts_test.go | 187 +++++++- test/tests/workspace/disk_test.go | 111 ++++- test/tests/workspace/example_test.go | 85 +++- test/tests/workspace/git_hooks_test.go | 123 +++++- test/tests/workspace/git_test.go | 200 ++++++++- test/tests/workspace/ports_test.go | 209 ++++++++- test/tests/workspace/process_priority_test.go | 136 +++++- 25 files changed, 2624 insertions(+), 159 deletions(-) create mode 100644 test/tests/ide/jetbrains/base_in_workspace_test.go create mode 100644 test/tests/ide/jetbrains/base_test.go create mode 100755 test/tests/ide/jetbrains/warmup-indexing.sh diff --git a/.github/actions/integration-tests/action.yml b/.github/actions/integration-tests/action.yml index 42cdc9fc9d3823..58be31aa4d5349 100644 --- a/.github/actions/integration-tests/action.yml +++ b/.github/actions/integration-tests/action.yml @@ -114,11 +114,11 @@ runs: paths: "test/**/TEST-*.xml" show: "all" if: always() - - name: Explain disabled IDE coverage + - name: Explain skipped IDE coverage if: ${{ always() && contains(fromJSON('["ide", "jetbrains", "vscode", "ssh", "all", ""]'), inputs.test_suite) }} shell: bash run: | - printf '%s\n' 'IDE integration coverage is disabled because it requires a GitHub user token. Skipped IDE tests do not validate IDE or SSH gateway functionality. See test/README.md for retained coverage.' >> "$GITHUB_STEP_SUMMARY" + printf '%s\n' 'GitHub-backed IDE tests skip in CI because no GitHub test-user credentials are supplied. They remain available for manually configured runs. Skipped tests do not validate IDE or SSH gateway functionality. See test/README.md.' >> "$GITHUB_STEP_SUMMARY" - name: Slack Notification uses: rtCamp/action-slack-notify@v2 if: ${{ (success() || failure()) && inputs.notify_slack_webhook != '' }} diff --git a/.github/workflows/ide-integration-tests.yml b/.github/workflows/ide-integration-tests.yml index 29ec026e426f89..2f72dcbe95b0e8 100644 --- a/.github/workflows/ide-integration-tests.yml +++ b/.github/workflows/ide-integration-tests.yml @@ -198,18 +198,18 @@ jobs: with: paths: "test/tests/**/TEST-*.xml" if: always() - - name: Explain disabled IDE coverage + - name: Explain skipped IDE coverage if: always() shell: bash run: | - printf '%s\n' 'All 13 IDE integration tests are disabled because they require a GitHub user token. This workflow checks deployment/readiness but provides no functional IDE or SSH gateway coverage. Skipped tests are not passing functional checks.' >> "$GITHUB_STEP_SUMMARY" + printf '%s\n' 'The 13 IDE integration tests skip in this workflow because no GitHub test-user credentials are supplied. They remain available for manually configured runs. Deployment/readiness and skipped-test reports provide no functional IDE or SSH gateway coverage.' >> "$GITHUB_STEP_SUMMARY" - name: Slack Notification uses: rtCamp/action-slack-notify@cdf0a2130cbcdfd82ba5fcac8e076370bf381b36 # pin@v2 if: success() || failure() env: SLACK_WEBHOOK: ${{ secrets.IDE_SLACK_WEBHOOK }} SLACK_COLOR: ${{ job.status }} - SLACK_MESSAGE: "IDE integration coverage disabled (GitHub user token removed). ${{ steps.test_summary.outputs.passed }} passed, ${{ steps.test_summary.outputs.failed }} failed, ${{ steps.test_summary.outputs.skipped }} skipped." + SLACK_MESSAGE: "GitHub-backed IDE tests skip in CI (no test-user credentials). ${{ steps.test_summary.outputs.passed }} passed, ${{ steps.test_summary.outputs.failed }} failed, ${{ steps.test_summary.outputs.skipped }} skipped." SLACK_FOOTER: "" delete: diff --git a/.github/workflows/preview-env-check-regressions.yml b/.github/workflows/preview-env-check-regressions.yml index b8f447459ed51c..52dfafedfea41a 100644 --- a/.github/workflows/preview-env-check-regressions.yml +++ b/.github/workflows/preview-env-check-regressions.yml @@ -147,11 +147,11 @@ jobs: with: paths: "test/tests/**/TEST.xml" if: always() - - name: Explain disabled workspace smoke coverage + - name: Explain skipped workspace smoke coverage if: always() shell: bash run: | - printf '%s\n' 'Workspace creation/image-build smoke coverage is disabled because it requires a GitHub user token. The remaining Gitpod API smoke tests require explicit opt-in and separate Gitpod credentials; this workflow does not enable them. A run containing only skipped tests provides no functional smoke coverage.' >> "$GITHUB_STEP_SUMMARY" + printf '%s\n' 'The workspace creation/image-build smoke test skips in CI because no GitHub test-user credentials are supplied; it remains available manually. Gitpod API smoke tests require explicit opt-in and separate Gitpod credentials, which this workflow does not supply. Skipped-only runs provide no functional smoke coverage.' >> "$GITHUB_STEP_SUMMARY" - id: auth if: failure() uses: google-github-actions/auth@955352c3b43196640b567e4646256d2fbb4aa1c7 # pin@v1 diff --git a/dev/jetbrains-test/README.md b/dev/jetbrains-test/README.md index 5bca99267dcacf..c4f3cb566bfb1e 100644 --- a/dev/jetbrains-test/README.md +++ b/dev/jetbrains-test/README.md @@ -1,13 +1,30 @@ -# JetBrains Integration Tests +## JetBrains Intergration Test -All JetBrains integration tests are disabled because they depend on a GitHub -user token. The test entry points remain as explicit skips; supplying a token -does not enable them. Their previous implementations are available in Git history. +See also [Internal Document](https://www.notion.so/gitpod/IDE-Integration-Tests-350235cc0db7489e86ebb57488a91f78) -`leeway run test:dev-intellij` reports this status without setting up GUI tools, -creating a preview, or requesting credentials. The IDE integration workflow -retains deployment/readiness checks and skipped-test reporting, but provides -no functional IDE coverage. +### How to trigger it manually? -See [the integration test documentation](../../test/README.md) for the disabled -tests and the workspace/component coverage that remains. +#### 1. With GHA + +- Trigger https://github.com/gitpod-io/gitpod/actions/workflows/ide-integration-tests.yml + +#### 2. In workspace with GHA + +- Create a preview env +```sh +TF_VAR_infra_provider=gce TF_VAR_with_large_vm=true leeway run dev:preview +``` +- Start tests +```sh +cd test/tests/ide/jetbrains +go test -v ./... -kubeconfig=/home/gitpod/.kube/config -namespace=default -username= +``` + +#### 3. In workspace + +- Open with Gitpod +- Create a preview env +```sh +TF_VAR_infra_provider=gce TF_VAR_with_large_vm=true leeway run dev:preview +``` +- Exec `leeway run test:dev-intellij` diff --git a/test/BUILD.yaml b/test/BUILD.yaml index 2ab79a5ddbc5d9..6a40722a6c49b7 100644 --- a/test/BUILD.yaml +++ b/test/BUILD.yaml @@ -9,6 +9,7 @@ packages: - go.mod - go.sum - "**/*.go" + - tests/ide/jetbrains/warmup-indexing.sh - leeway-build.sh deps: - components/common-go:lib @@ -45,7 +46,32 @@ packages: - ${imageRepoBase}/integration-tests:commit-${__git_commit} scripts: - name: dev-intellij - description: Report disabled IntelliJ IDEA integration coverage. + description: Start IntelliJ IDEA intergration tests in workspace. script: | - echo "JetBrains integration tests are disabled because they depend on a GitHub user token." - echo "No IDE functionality is tested. See test/README.md for retained integration coverage." + which gp-vncsession &>/dev/null || leeway run dev/jetbrains-test:install-gui-dependencies + echo -e "📣 Access GUI on $(gp url 6080)\n\n" + [ "$(git rev-parse --abbrev-ref HEAD)" = "main" ] && echo "❌ Please create new branch" && exit 1 + + export DISPLAY=:0 + previewUrl=$(previewctl get url) + ok=true + useLatest=false + curl -s -o /dev/null -w "%{http_code}" "$previewUrl/api/version" --max-time 1 | grep -q "200" || { echo -e "❌ Preview env is not ready yet, try create one:\nTF_VAR_infra_provider="gce" TF_VAR_with_large_vm=true leeway run dev:preview\n"; exit 1; } + echo "✅ Preview env $previewUrl/workspaces is ready" + [ -z "$USER_TOKEN" ] && echo "❌ env USER_TOKEN is not set or is empty. Create one PAT $previewUrl/user/tokens" && ok=false || echo "✅ PAT is set" + [ -z "$USERNAME" ] && echo "❌ env USERNAME is not set" && ok=false || echo "✅ User is set" + $ok || exit 1; + + options=("latest" "stable") + echo "Select editor version: 1) latest[default]; 2) stable: " + read -p "$REPLY" choice + choice=${choice:-1} + option=${options[choice-1]} + useLatest=$([ "$option" == "latest" ] && true || false) + + echo "🚢 Starting intergration tests for IntelliJ $option..." + if [ "$DEBUG" = "true" ]; then + TEST_USE_LATEST_VERSION=$useLatest TEST_IN_WORKSPACE=true ROBOQUAT_TOKEN=skip dlv test /workspace/gitpod/test/tests/ide/jetbrains --headless --listen=:32991 --api-version=2 -- -test.timeout=60m -test.v -test.run=^TestIntelliJWarmup -kubeconfig=$HOME/.kube/config -namespace=default -username=$USERNAME + else + TEST_USE_LATEST_VERSION=$useLatest TEST_IN_WORKSPACE=true ROBOQUAT_TOKEN=skip go test -timeout 60m -v -run ^TestGoLand ./tests/ide/jetbrains -kubeconfig=$HOME/.kube/config -namespace=default -username=$USERNAME + fi diff --git a/test/README.md b/test/README.md index 337cb8260f5dd5..045bbe1dd172e4 100644 --- a/test/README.md +++ b/test/README.md @@ -22,23 +22,19 @@ Such tests are for example: ## Automatically at Gitpod -The GitHub Actions **Branch Build** workflow can run integration tests against a -preview environment. To run the server and database (`webapp`) suite, select this -option in the PR description: +The **Branch Build** workflow runs webapp tests when the PR description selects: ```markdown - [x] with-integration-tests=webapp ``` -This enables a preview with a large VM, builds and deploys the branch, and runs -`./test/run.sh -s webapp` through the shared integration-test action. The option -also accepts `all` to include webapp with the other suites. +This builds and deploys the branch to a large preview and runs the server/database +suite. The **Workspace integration tests** workflow always runs `workspace`. -The **Workspace integration tests** workflow always runs `workspace`. There is -no separate scheduled webapp workflow. - -> Retained tests use builtin or temporary users by default. An explicitly selected -> `username` must already exist in the preview database. +CI does not supply GitHub test-user credentials. Tests requiring them skip; +other workspace, component, and webapp tests continue to run. Default IDE and +workspace-creation smoke runs have no functional coverage when all tests skip. +The implementations and manual entry points remain available. ## Manually @@ -53,13 +49,12 @@ This is best for when you're actively developing Gitpod. Test will work if images that they use are already cached by Gitpod instance. If not, they might fail if it takes too long to pull an image. -The default suites use builtin or temporary Gitpod users. They do not require a -GitHub user token or a pre-existing GitHub-authenticated test user. Some retained -tests still clone public repositories anonymously and pull container images. +There are 4 different types of tests: -Enterprise-specific tests retain their `-enterprise=true` opt-in. An explicit -`USER_NAME` or `-username` remains available for tests that support selecting an -existing Gitpod user, but the runner no longer fetches a user or token from secrets. +1. Enterprise specific, that require valid license to be installed. Run those with `-enterprise=true` +2. Tests that require correct user (user should have github OAuth integration setup with gitpod). Run those with `-username=`. Make sure to load https://github.com/gitpod-io/gitpod-test-repo and https://github.com/gitpod-io/gitpod workspaces inside your gitpod that you are testing to preload those images onto your node. Wait for it to finish pulling those image, this will ensure that test will not fail due to timeout while waiting to pull an image for the first time. +3. To test gitlab integration, add `-gitlab=true` +4. All other tests. If you want to run an entire test suite, the easiest is to use `./test/run.sh`: @@ -74,92 +69,48 @@ If you want to run an entire test suite, the easiest is to use `./test/run.sh`: ./test/run.sh -s webapp -r report.csv ``` -If you're iterating on a single retained test: +If you're iterating on a single test, the easiest is to use `go test` directly. + +For GitHub-backed tests, explicitly supply `USER_NAME` (or `-username`) and +`USER_TOKEN`, where `USER_TOKEN` is a **GitHub user token**. The runner preserves +these manual inputs and no longer loads credentials from CI environment aliases +or the Kubernetes test-user secret. Use a preview with a working GitHub auth +provider. Disk tests require the selected user to already have a usable GitHub +identity/token in the preview database; they skip when no username is supplied. + +```sh +export USER_NAME='' +export USER_TOKEN='' +./test/run.sh -s workspace +``` + +Without these variables, credential-dependent tests skip and the remaining tests +use their builtin or temporary user paths. IDE tests retain their additional +setup requirements; see [JetBrains manual instructions](../dev/jetbrains-test/README.md). + +The opt-in collaborator smoke tests use `USER_TOKEN` for a different purpose: a +**Gitpod PAT or session cookie**, with `TEST_COLLABORATOR=true`. Temporary-token +smoke tests use `INSTALLATION_ADMIN_PAT` / `MEMBER_USER_PAT` and +`TEST_CREATE_TMP_TOKEN=true`. Those interfaces are unchanged. ```console cd test -go test -v ./tests/workspace \ - -kubeconfig=/home/gitpod/.kube/config \ +go test -v ./... \ + -run \ -namespace=default \ - -run '^TestLaunchWorkspaceDirectly$' + -username= \ + -enterprise= \ + -gitlab= ``` -Package setup still checks Kubernetes/Gitpod readiness before running tests, -including packages whose tests are all skipped. Use `go test -c` to compile a -package without executing that setup. - -## Disabled GitHub user-token coverage - -The following test entry points are explicit skip stubs. Providing a token does -not re-enable them. Their previous implementations are available in Git history. - -| Suite/package | Disabled tests | -|:--------------|:---------------| -| Workspace: ws-manager | `TestDotfiles` | -| Workspace: ws-daemon | `TestNetworkLimiting` | -| Workspace: runtime | `TestGitHubContexts`, `TestGitLabContexts`, `TestDiskActions`, `TestWorkspaceInstrumentation`, `TestGitHooks`, `TestGitActions`, `TestRegularWorkspacePorts`, `TestProcessPriority` | -| IDE: SSH | `TestSSHGatewayConnection` | -| IDE: VS Code | `TestPythonExtWorkspace` | -| IDE: JetBrains | `TestGoLand`, `TestIntellij`, `TestPhpStorm`, `TestPyCharm`, `TestRubyMine`, `TestWebStorm`, `TestRider`, `TestCLion`, `TestRustRover`, `TestIntellijNotPreconfiguredRepo`, `TestIntelliJWarmup` | -| Smoke | `TestStartWorkspaceWithImageBuild` | - -GitLab context tests shared the GitHub user-token fixture; this does not mean a -GitHub token authenticates to GitLab. Disk-quota tests indirectly relied on the -shared authenticated user, despite having no token guard of their own. - -CI no longer supplies the GitHub test-user credentials, and `run.sh` no longer -loads them from CI environment variables or the Kubernetes test-user secret. -Gitpod API tokens generated inside the test framework remain available. - -## Remaining component coverage - -This table counts enabled top-level test entry points retained after removing the -GitHub user-token dependency. It is not line or branch coverage. Already skipped -or opt-in tests are excluded from the counts. - -| Component/area | Tests before → after | Retained | Remaining checks | -|:---------------|:---------------------|:---------|:-----------------| -| ws-manager | 14 → 13 | 93% | Lifecycle, backups, maintenance, repositories, Git status, tasks, protected secrets, prebuilds | -| ws-daemon | 5 → 4 | 80% | CPU burst, I/O limits, FUSE, bucket creation | -| content-service | 3 → 3 | 100% | Upload/download URLs and blob round trips | -| image-builder | 2 → 2 | 100% | Base-image builds and concurrent builds | -| server (`webapp`) | 2 → 1 | 50% | Authenticated `GetLoggedInUser` | -| database (`webapp`) | 1 → 1 | 100% | Builtin workspace user exists | -| Workspace runtime | 14 → 7 | 50% | Direct launch, cgroups, process limits, ephemeral storage, `/proc`, Docker, `gp top` | -| JetBrains / VS Code / SSH IDE suites | 13 → 0 | 0% | None | -| Default workspace/image-build smoke flow | 1 → 0 | 0% | None | - -The workspace suite still runs its regular and maintenance passes. It retains 30 -top-level test functions, including the already-skipped K3s test. Other existing -conditions, such as Docker Hub rate limiting, can affect actual execution. -`TestLaunchWorkspaceDirectly` remains active alongside the disabled -`TestWorkspaceInstrumentation` in the same source file. - -The `webapp` suite's `TestStartWorkspace` is retained for explicitly configured -users, but skips by default without a username; it still needs a GitHub context. -`TestAdminBlockUser` requires the enterprise flag. Consequently the default webapp -checks are `TestServerAccess` and `TestBuiltinUserExists`. - -IDE workflows have no active functional tests. The default preview-regression -smoke workflow also has no active functional tests. Their deployment/readiness -checks and skipped-test reports do not validate IDE, SSH gateway, or user-facing -workspace-creation behavior. The workflows report these limitations explicitly. - -## Opt-in smoke tests using Gitpod credentials - -These tests do not use the removed GitHub credential and remain available through -`go test` in `test/tests/smoke-test`: - -- `TestMembers`, `TestProjects`, and `TestGetProject` use a **Gitpod PAT or session - cookie** supplied as `USER_TOKEN`, with `TEST_COLLABORATOR=true`. See - [collaborator_test.go](tests/smoke-test/collaborator_test.go) for setup. -- The six `TestCreateTemporaryAccessToken*` tests use `INSTALLATION_ADMIN_PAT` - and/or `MEMBER_USER_PAT`, with `TEST_CREATE_TMP_TOKEN=true`. See - [papi_create_temp_token_test.go](tests/smoke-test/papi_create_temp_token_test.go). - -These opt-in flags are not enabled by the default smoke workflow. GitHub Actions' -`GITHUB_TOKEN` and other infrastructure credentials are separate from both these -Gitpod credentials and the removed GitHub user credential. +A concrete example would be + +```console +cd test +go test -v ./... \ + -namespace=default \ + -run TestWorkspaceInstrumentation +``` # Tips diff --git a/test/pkg/integration/apis.go b/test/pkg/integration/apis.go index 620d57bac75593..a736e723c3bb2f 100644 --- a/test/pkg/integration/apis.go +++ b/test/pkg/integration/apis.go @@ -15,6 +15,7 @@ import ( "database/sql" "encoding/base64" "encoding/json" + "errors" "fmt" "io" "net" @@ -458,6 +459,115 @@ func (c *ComponentAPI) UpdateUserFeatureFlag(userId, featureFlag string) error { return nil } +func (c *ComponentAPI) CreateUser(username string, token string) (string, error) { + dbConfig, err := FindDBConfigFromPodEnv("server", c.namespace, c.client) + if err != nil { + return "", err + } + + db, err := c.DB() + if err != nil { + return "", err + } + + var userId string + err = db.QueryRow(`SELECT id FROM d_b_user WHERE name = ? and markedDeleted != 1 and blocked != 1`, username).Scan(&userId) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return "", err + } + + if userId == "" { + userUuid, err := uuid.NewRandom() + if err != nil { + return "", err + } + + userId = userUuid.String() + _, err = db.Exec(`INSERT IGNORE INTO d_b_user (id, creationDate, avatarUrl, name, fullName, featureFlags, lastVerificationTime) VALUES (?, ?, ?, ?, ?, ?, ?)`, + userId, + time.Now().Format(time.RFC3339), + "", + username, + username, + "{\"permanentWSFeatureFlags\":[]}", + time.Now().Format(time.RFC3339), + ) + if err != nil { + return "", err + } + } + + var authId string + err = db.QueryRow(`SELECT authId FROM d_b_identity WHERE userId = ?`, userId).Scan(&authId) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return "", err + } + if authId == "" { + authId = strconv.FormatInt(time.Now().UnixMilli(), 10) + _, err = db.Exec(`INSERT IGNORE INTO d_b_identity (authProviderId, authId, authName, userId) VALUES (?, ?, ?, ?)`, + "Public-GitHub", + authId, + username, + userId, + ) + if err != nil { + return "", err + } + } + + var cnt int + err = db.QueryRow(`SELECT COUNT(1) AS cnt FROM d_b_token_entry WHERE authId = ?`, authId).Scan(&cnt) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return "", err + } + if cnt == 0 { + uid, err := uuid.NewRandom() + if err != nil { + return "", err + } + + // Double Marshalling to be compatible with EncryptionServiceImpl + value := struct { + Value string `json:"value"` + Scopes []string `json:"scopes"` + }{ + Value: token, + Scopes: []string{"user:email", "read:user", "public_repo"}, + } + valueBytes, err := json.Marshal(value) + if err != nil { + return "", err + } + valueBytes2, err := json.Marshal(string(valueBytes)) + if err != nil { + return "", err + } + + encryptedData, iv := EncryptValue(valueBytes2, dbConfig.EncryptionKeys.Material) + encrypted := EncriptedDBData{} + encrypted.Data = encryptedData + encrypted.KeyParams.Iv = iv + encrypted.KeyMetadata.Name = dbConfig.EncryptionKeys.Metadata.Name + encrypted.KeyMetadata.Version = dbConfig.EncryptionKeys.Metadata.Version + encryptedJson, err := json.Marshal(encrypted) + if err != nil { + return "", err + } + + _, err = db.Exec(`INSERT IGNORE INTO d_b_token_entry (authProviderId, authId, token, uid) VALUES (?, ?, ?, ?)`, + "Public-GitHub", + authId, + encryptedJson, + uid.String(), + ) + if err != nil { + return "", err + } + } + + return userId, nil +} + func (c *ComponentAPI) createGitpodToken(user string, scopes []string) (tkn string, err error) { id, err := c.GetUserId(user) if err != nil { diff --git a/test/pkg/integration/setup.go b/test/pkg/integration/setup.go index 98be29dd8fbc68..6a1f94f8fe26cb 100644 --- a/test/pkg/integration/setup.go +++ b/test/pkg/integration/setup.go @@ -32,6 +32,12 @@ func SkipWithoutUsername(t *testing.T, username string) { } } +func SkipWithoutUserToken(t *testing.T, userToken string) { + if userToken == "" { + t.Skip("Skipping because requires a user token") + } +} + func SkipWithoutEnterpriseLicense(t *testing.T, enterpise bool) { if !enterpise { t.Skip("Skipping because requires enterprise license") diff --git a/test/run.sh b/test/run.sh index 0d4fcfe2e9a7b6..422904e6c6eeaf 100755 --- a/test/run.sh +++ b/test/run.sh @@ -75,12 +75,6 @@ case $TEST_SUITE in exit 1 esac -case $TEST_SUITE in - ide|jetbrains|vscode|ssh|all|"") - echo "IDE integration tests are disabled: skipped tests provide no IDE or SSH gateway coverage. See test/README.md." - ;; -esac - args=() if [ "${REPORT}" != "" ]; then args+=( "--json" ) diff --git a/test/tests/components/ws-daemon/network_limiting_test.go b/test/tests/components/ws-daemon/network_limiting_test.go index 2958cc1cf0c479..a2b1905cf77ee8 100644 --- a/test/tests/components/ws-daemon/network_limiting_test.go +++ b/test/tests/components/ws-daemon/network_limiting_test.go @@ -4,8 +4,101 @@ package wsdaemon -import "testing" +import ( + "context" + "os" + "testing" + "time" + + "github.com/gitpod-io/gitpod/common-go/kubernetes" + daemon "github.com/gitpod-io/gitpod/test/pkg/agent/daemon/api" + "github.com/gitpod-io/gitpod/test/pkg/integration" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" +) func TestNetworkLimiting(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + userToken, _ := os.LookupEnv("USER_TOKEN") + integration.SkipWithoutUsername(t, username) + integration.SkipWithoutUserToken(t, userToken) + + f := features.New("network limiting"). + WithLabel("component", "ws-daemon"). + Assess("verify if network limiting works fine", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + t.Parallel() + + ctx, cancel := context.WithTimeout(testCtx, 5*time.Minute) + defer cancel() + + api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) + t.Cleanup(func() { + api.Done(t) + }) + + _, err := api.CreateUser(username, userToken) + if err != nil { + t.Fatal(err) + } + + ws, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, "https://github.com/gitpod-io/empty", username, api, integration.WithGitpodUser(username)) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer scancel() + + sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer sapi.Done(t) + + _, err = stopWs(true, sapi) + if err != nil { + t.Fatal(err) + } + }) + + daemonClient, daemonCloser, err := integration.Instrument(integration.ComponentWorkspaceDaemon, "daemon", cfg.Namespace(), kubeconfig, cfg.Client(), + integration.WithWorkspacekitLift(false), + integration.WithContainer("ws-daemon"), + ) + if err != nil { + t.Fatalf("unexpected error instrumenting daemon: %v", err) + } + defer daemonClient.Close() + integration.DeferCloser(t, daemonCloser) + + t.Logf("checking if workspace pod has network limit annotation") + var pod corev1.Pod + if err := cfg.Client().Resources().Get(ctx, "ws-"+ws.LatestInstance.ID, cfg.Namespace(), &pod); err != nil { + t.Fatal(err) + } + annotation, ok := pod.Annotations[kubernetes.WorkspaceNetConnLimitAnnotation] + if !ok { + t.Fatalf("expected annotation %s to be present on workspace pod but wasn't", kubernetes.WorkspaceNetConnLimitAnnotation) + } + if annotation != "true" { + t.Fatalf("expected annotation %s to be true but was %s", kubernetes.WorkspaceNetConnLimitAnnotation, annotation) + } + + t.Logf("checking nftable rules for rate limiting") + containerId := getCalicoContainerId(&pod) + var resp daemon.VerifyRateLimitingRuleResponse + err = daemonClient.Call("DaemonAgent.VerifyRateLimitingRule", daemon.VerifyRateLimitingRuleRequest{ + ContainerId: containerId, + }, &resp) + if err != nil { + t.Errorf("error verifying rate limiting rule for container %s: %v", containerId, err) + } + + t.Logf("verified rate limiting rule") + + return testCtx + }).Feature() + + testEnv.Test(t, f) +} + +func getCalicoContainerId(pod *corev1.Pod) string { + return pod.Annotations["cni.projectcalico.org/containerID"] } diff --git a/test/tests/components/ws-manager/dotfiles_test.go b/test/tests/components/ws-manager/dotfiles_test.go index 790e1638671f61..0e4b5c8a6acce6 100644 --- a/test/tests/components/ws-manager/dotfiles_test.go +++ b/test/tests/components/ws-manager/dotfiles_test.go @@ -4,8 +4,189 @@ package wsmanager -import "testing" +import ( + "context" + "encoding/json" + "fmt" + "os" + "strings" + "testing" + "time" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/e2e-framework/klient" + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + csapi "github.com/gitpod-io/gitpod/content-service/api" + agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" + "github.com/gitpod-io/gitpod/test/pkg/integration" + wsmanapi "github.com/gitpod-io/gitpod/ws-manager/api" +) func TestDotfiles(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + userToken, _ := os.LookupEnv("USER_TOKEN") + integration.SkipWithoutUsername(t, username) + integration.SkipWithoutUserToken(t, userToken) + + f := features.New("dotfiles").WithLabel("component", "ws-manager").Assess("ensure dotfiles are loaded", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + t.Parallel() + + ctx, cancel := context.WithTimeout(testCtx, 5*time.Minute) + defer cancel() + + api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) + t.Cleanup(func() { + api.Done(t) + }) + + userId, err := api.CreateUser(username, userToken) + if err != nil { + t.Fatal(err) + } + + // Scopes should larger than https://github.com/gitpod-io/gitpod/blob/main/components/supervisor/pkg/serverapi/publicapi.go#L99-L109 + tokenId, err := api.CreateOAuth2Token(username, []string{ + "function:getToken", + "function:openPort", + "function:getOpenPorts", + "function:guessGitTokenScopes", + "function:getWorkspace", + "function:sendHeartBeat", + "function:trackEvent", + "resource:token::*::get", + }) + if err != nil { + t.Fatal(err) + } + + swr := func(req *wsmanapi.StartWorkspaceRequest) error { + req.Spec.Envvars = append(req.Spec.Envvars, + &wsmanapi.EnvironmentVariable{ + Name: "SUPERVISOR_DOTFILE_REPO", + Value: "https://github.com/gitpod-io/test-dotfiles-support", + }, + &wsmanapi.EnvironmentVariable{ + Name: "THEIA_SUPERVISOR_TOKENS", + Value: fmt.Sprintf(`[{ + "token": "%v", + "kind": "gitpod", + "host": "%v", + "scope": ["function:getToken", "function:openPort", "function:sendHeartBeat", "function:getOpenPorts", "function:guessGitTokenScopes", "function:getWorkspace", "function:trackEvent", "resource:token::*::get"], + "expiryDate": "2026-10-26T10:38:05.232Z", + "reuse": 4 + }]`, tokenId, getHostUrl(ctx, t, cfg.Client(), cfg.Namespace())), + }, + ) + + req.Spec.Initializer = &csapi.WorkspaceInitializer{ + Spec: &csapi.WorkspaceInitializer_Git{ + Git: &csapi.GitInitializer{ + RemoteUri: "https://github.com/gitpod-io/empty", + CheckoutLocation: "empty", + Config: &csapi.GitConfig{}, + }, + }, + } + + req.Metadata.Owner = userId + req.Spec.WorkspaceLocation = "empty" + return nil + } + + ws, stopWs, err := integration.LaunchWorkspaceDirectly(t, ctx, api, integration.WithRequestModifier(swr)) + if err != nil { + t.Fatal(err) + } + + defer func() { + sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer scancel() + + sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer sapi.Done(t) + + _, err = stopWs(true, sapi) + if err != nil { + t.Errorf("cannot stop workspace: %q", err) + } + }() + + rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), + integration.WithInstanceID(ws.Req.Id), + integration.WithContainer("workspace"), + integration.WithWorkspacekitLift(true), + ) + if err != nil { + t.Fatal(err) + } + + integration.DeferCloser(t, closer) + defer rsa.Close() + + assertDotfiles(t, rsa) + + return testCtx + }).Feature() + + testEnv.Test(t, f) +} + +func getHostUrl(ctx context.Context, t *testing.T, k8sClient klient.Client, namespace string) string { + var configmap corev1.ConfigMap + if err := k8sClient.Resources().Get(ctx, "server-config", namespace, &configmap); err != nil { + t.Fatal(err) + } + + config, ok := configmap.Data["config.json"] + if !ok { + t.Fatal("server config map does not contain config.json") + } + + c := make(map[string]json.RawMessage) + if err := json.Unmarshal([]byte(config), &c); err != nil { + t.Fatal(err) + } + + hostUrlRaw, ok := c["hostUrl"] + if !ok { + t.Fatal("server config map does not contain host url") + } + + return strings.TrimPrefix(strings.Trim(string(hostUrlRaw), "\""), "https://") +} + +func assertDotfiles(t *testing.T, rsa *integration.RpcClient) error { + var ls agent.ListDirResponse + err := rsa.Call("WorkspaceAgent.ListDir", &agent.ListDirRequest{ + Dir: "/home/gitpod/.dotfiles", + }, &ls) + + if err != nil { + t.Fatal(err) + } + + dotfiles := map[string]bool{ + "bash_aliases": false, + "git": false, + } + + for _, dir := range ls.Files { + delete(dotfiles, dir) + } + + if len(dotfiles) > 0 { + var cat agent.ExecResponse + err := rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ + Dir: "/", + Command: "cat", + Args: []string{"/home/gitpod/.dotfiles.log"}, + }, &cat) + if err == nil { + t.Fatalf("dotfiles were not installed successfully: %+v, .dotfiles.log: %s", dotfiles, cat.Stdout) + } + t.Fatalf("dotfiles were not installed successfully: %+v", dotfiles) + } + + return nil } diff --git a/test/tests/ide/jetbrains/base_in_workspace_test.go b/test/tests/ide/jetbrains/base_in_workspace_test.go new file mode 100644 index 00000000000000..c6254b46fa89dc --- /dev/null +++ b/test/tests/ide/jetbrains/base_in_workspace_test.go @@ -0,0 +1,49 @@ +// Copyright (c) 2024 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License.AGPL.txt in the project root for license information. + +package ide + +import ( + "context" + "fmt" + "io" + "os" + "os/exec" + "testing" +) + +type testLogWriter struct { + t *testing.T +} + +var _ io.Writer = &testLogWriter{} + +func (t *testLogWriter) Write(p []byte) (n int, err error) { + t.t.Log(string(p)) + return len(p), nil +} + +const localDebug = false + +func testWithoutGithubAction(ctx context.Context, gatewayLink, gitpodAccessToken, secretEndpoint string, useLatest bool) { + scriptName := "dev/jetbrains-test:test-stable" + if useLatest { + scriptName = "dev/jetbrains-test:test-latest" + } + + if localDebug { + fmt.Printf("Exec command below to run UI tests:\n\nexport DISPLAY=:0\nexport GATEWAY_LINK=\"%s\"\nexport GITPOD_TEST_ACCESSTOKEN=\"%s\"\nexport WS_ENDPOINT=%s\nleeway run %s -Dversion=integration-test -DpublishToJBMarketplace=false", gatewayLink, gitpodAccessToken, secretEndpoint, scriptName) + os.Exit(1) + } + cmdEnv := os.Environ() + cmdEnv = append(cmdEnv, "GATEWAY_LINK="+gatewayLink) + cmdEnv = append(cmdEnv, "GITPOD_TEST_ACCESSTOKEN="+gitpodAccessToken) + cmdEnv = append(cmdEnv, "WS_ENDPOINT="+secretEndpoint) + cmd := exec.CommandContext(ctx, "leeway", "run", scriptName, "-Dversion=integration-test", "-DpublishToJBMarketplace=false") + cmd.Env = cmdEnv + // writer := &testLogWriter{t: t} + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stdout + cmd.Run() +} diff --git a/test/tests/ide/jetbrains/base_test.go b/test/tests/ide/jetbrains/base_test.go new file mode 100644 index 00000000000000..82df464eb77fb1 --- /dev/null +++ b/test/tests/ide/jetbrains/base_test.go @@ -0,0 +1,412 @@ +// Copyright (c) 2024 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License.AGPL.txt in the project root for license information. + +package ide + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "regexp" + "strings" + "testing" + "time" + + "golang.org/x/oauth2" + "sigs.k8s.io/e2e-framework/pkg/envconf" + + protocol "github.com/gitpod-io/gitpod/gitpod-protocol" + supervisor "github.com/gitpod-io/gitpod/supervisor/api" + agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" + "github.com/gitpod-io/gitpod/test/pkg/integration" + wsmanapi "github.com/gitpod-io/gitpod/ws-manager/api" + "github.com/google/go-github/v42/github" +) + +var testBaseConfig = map[string]struct{ ProductCode, Repo string }{ + "goland": {"GO", "https://github.com/gitpod-samples/template-golang-cli"}, + "intellij": {"IU", "https://github.com/jeanp413/spring-petclinic"}, + "phpstorm": {"PS", "https://github.com/gitpod-samples/template-php-laravel-mysql"}, + "pycharm": {"PY", "https://github.com/gitpod-samples/template-python-django"}, + // TODO: open comment after https://github.com/gitpod-io/gitpod/issues/16302 resolved + // "rubymine": {"RM", "https://github.com/gitpod-samples/template-ruby-on-rails-postgres"}, + "rubymine": {"RM", "https://github.com/gitpod-io/Gitpod-Ruby-On-Rails"}, + "webstorm": {"WS", "https://github.com/gitpod-samples/template-typescript-react"}, + "rider": {"RD", "https://github.com/gitpod-samples/template-dotnet-core-cli-csharp"}, + "clion": {"CL", "https://github.com/gitpod-samples/template-cpp"}, + "rustrover": {"RR", "https://github.com/gitpod-samples/template-rust-cli"}, +} + +var ( + userToken string + roboquatToken string +) + +func init() { + userToken, _ = os.LookupEnv("USER_TOKEN") + roboquatToken, _ = os.LookupEnv("ROBOQUAT_TOKEN") +} + +type JetBrainsIDETestOpts struct { + IDE string + ProductCode string + Repo string + AdditionalRpcCalls []func(rsa *integration.RpcClient, jbCtx *JetBrainsTestCtx) error + BeforeWorkspaceStart func(userID string) error + RepositoryID string +} + +type JetBrainsIDETestOpt func(*JetBrainsIDETestOpts) error + +func WithAdditionRpcCall(f func(rsa *integration.RpcClient, jbCtx *JetBrainsTestCtx) error) JetBrainsIDETestOpt { + return func(o *JetBrainsIDETestOpts) error { + o.AdditionalRpcCalls = append(o.AdditionalRpcCalls, f) + return nil + } +} + +func WithIDE(ide string) JetBrainsIDETestOpt { + return func(o *JetBrainsIDETestOpts) error { + o.IDE = ide + if t, ok := testBaseConfig[ide]; ok { + o.ProductCode = t.ProductCode + if o.Repo == "" { + o.Repo = t.Repo + } + } + return nil + } +} + +func WithRepo(repo string) JetBrainsIDETestOpt { + return func(o *JetBrainsIDETestOpts) error { + o.Repo = repo + return nil + } +} + +func WithRepositoryID(repoID string) JetBrainsIDETestOpt { + return func(o *JetBrainsIDETestOpts) error { + o.RepositoryID = repoID + return nil + } +} + +func BaseGuard(t *testing.T) { + integration.SkipWithoutUsername(t, username) + integration.SkipWithoutUserToken(t, userToken) + if roboquatToken == "" { + t.Fatal("this test need github action run permission") + } +} + +func JetBrainsIDETest(ctx context.Context, t *testing.T, cfg *envconf.Config, opts ...JetBrainsIDETestOpt) { + BaseGuard(t) + option := &JetBrainsIDETestOpts{} + for _, o := range opts { + if err := o(option); err != nil { + t.Fatal(err) + } + } + + api, server, _, _ := MustConnectToServer(ctx, t, cfg) + var err error + + t.Logf("starting workspace") + var info *protocol.WorkspaceInfo + var stopWs func(waitForStop bool, api *integration.ComponentAPI) (*wsmanapi.WorkspaceStatus, error) + useLatest := os.Getenv("TEST_USE_LATEST_VERSION") == "true" + for i := 0; i < 3; i++ { + info, stopWs, err = integration.LaunchWorkspaceWithOptions(t, ctx, &integration.LaunchWorkspaceOptions{ + ContextURL: option.Repo, + ProjectID: option.RepositoryID, + IDESettings: &protocol.IDESettings{ + DefaultIde: option.IDE, + UseLatestVersion: useLatest, + }, + }, username, api) + if err != nil { + if strings.Contains(err.Error(), "code 429 message: too many requests") { + t.Log(err) + time.Sleep(10 * time.Second) + continue + } + t.Fatal(err) + } else { + break + } + } + + defer func() { + sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer scancel() + + sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer sapi.Done(t) + + _, _ = stopWs(true, sapi) + }() + + t.Logf("get oauth2 token") + oauthToken, err := api.CreateOAuth2Token(username, []string{ + "function:getGitpodTokenScopes", + "function:getIDEOptions", + "function:getOwnerToken", + "function:getWorkspace", + "function:getWorkspaces", + "function:listenForWorkspaceInstanceUpdates", + "resource:default", + }) + if err != nil { + t.Fatal(err) + } + + t.Logf("resolve owner token") + ownerToken, err := server.GetOwnerToken(ctx, info.LatestInstance.WorkspaceID) + if err != nil { + t.Fatal(err) + } + + t.Logf("resolve desktop IDE link") + gatewayLink, err := resolveDesktopIDELink(info, option.IDE, ownerToken, t) + if err != nil { + t.Fatal(err) + } + + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: roboquatToken}, + ) + tc := oauth2.NewClient(ctx, ts) + + githubClient := github.NewClient(tc) + + if os.Getenv("TEST_IN_WORKSPACE") == "true" { + t.Logf("run test in workspace") + go testWithoutGithubAction(ctx, gatewayLink, oauthToken, strings.TrimPrefix(info.LatestInstance.IdeURL, "https://"), useLatest) + } else { + t.Logf("trigger github action") + // Note: For manually trigger github action purpose + // t.Logf("secret_gateway_link %s\nsecret_access_token %s\nsecret_endpoint %s\njb_product %s\nuse_latest %v\nbuild_id %s\nbuild_url %s", gatewayLink, oauthToken, strings.TrimPrefix(info.LatestInstance.IdeURL, "https://"), option.IDE, useLatest, os.Getenv("TEST_BUILD_ID"), os.Getenv("TEST_BUILD_URL")) + // time.Sleep(30 * time.Minute) + _, err = githubClient.Actions.CreateWorkflowDispatchEventByFileName(ctx, "gitpod-io", "gitpod", "jetbrains-integration-test.yml", github.CreateWorkflowDispatchEventRequest{ + Ref: os.Getenv("TEST_BUILD_REF"), + Inputs: map[string]interface{}{ + "secret_gateway_link": gatewayLink, + "secret_access_token": oauthToken, + "secret_endpoint": strings.TrimPrefix(info.LatestInstance.IdeURL, "https://"), + "jb_product": option.IDE, + "use_latest": fmt.Sprintf("%v", useLatest), + "build_id": os.Getenv("TEST_BUILD_ID"), + "build_url": os.Getenv("TEST_BUILD_URL"), + }, + }) + if err != nil { + t.Fatal(err) + } + } + + checkUrl := fmt.Sprintf("https://63342-%s/codeWithMe/unattendedHostStatus?token=gitpod", strings.TrimPrefix(info.LatestInstance.IdeURL, "https://")) + + t.Logf("waiting result") + testStatus := false + for ctx.Err() == nil { + time.Sleep(1 * time.Second) + body, _ := getHttpContent(checkUrl, ownerToken) + var status gatewayHostStatus + err = json.Unmarshal(body, &status) + if err != nil { + continue + } + if len(status.Projects) == 1 && status.Projects[0].ControllerConnected { + testStatus = true + break + } + } + if !testStatus { + t.Fatal(ctx.Err()) + } + + rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), integration.WithInstanceID(info.LatestInstance.ID), integration.WithWorkspacekitLift(true)) + if err != nil { + t.Fatal(err) + } + defer rsa.Close() + integration.DeferCloser(t, closer) + + fatalMessages := []string{} + + checkIDEALogs := func() { + qualifier := "" + if useLatest { + qualifier = "-latest" + } + jbSystemDir := fmt.Sprintf("/workspace/.cache/JetBrains%s/RemoteDev-%s", qualifier, option.ProductCode) + ideaLogPath := jbSystemDir + "/log/idea.log" + + t.Logf("Check idea.log file correct location %s", ideaLogPath) + + var resp agent.ExecResponse + err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ + Dir: "/", + Command: "bash", + Args: []string{ + "-c", + fmt.Sprintf("cat %s", ideaLogPath), + }, + }, &resp) + + t.Logf("checking idea.log") + if err != nil || resp.ExitCode != 0 { + t.Fatal("idea.log file not found in the expected location") + } + + pluginLoadedRegex := regexp.MustCompile(`Loaded custom plugins:.* (Gitpod Remote|gitpod-remote)`) + pluginStartedRegex := regexp.MustCompile(`Gitpod gateway link`) + pluginIncompatibleRegex := regexp.MustCompile(`Plugin '(Gitpod Remote|gitpod-remote)' .* is not compatible`) + + ideaLogs := []byte(resp.Stdout) + if pluginLoadedRegex.Match(ideaLogs) { + t.Logf("backend-plugin loaded") + } else { + fatalMessages = append(fatalMessages, "backend-plugin not loaded") + } + if pluginStartedRegex.Match(ideaLogs) { + t.Logf("backend-plugin started") + } else { + fatalMessages = append(fatalMessages, "backend-plugin not started") + } + if pluginIncompatibleRegex.Match(ideaLogs) { + fatalMessages = append(fatalMessages, "backend-plugin is incompatible") + } else { + t.Logf("backend-plugin maybe compatible") + } + + for _, fn := range option.AdditionalRpcCalls { + if err := fn(rsa, &JetBrainsTestCtx{ + SystemDir: jbSystemDir, + }); err != nil { + fatalMessages = append(fatalMessages, fmt.Sprintf("additional agent exec failed: %v", err)) + } + } + } + checkIDEALogs() + + if len(fatalMessages) > 0 { + t.Fatalf("[error] tests fail: \n%s", strings.Join(fatalMessages, "\n")) + } +} + +func resolveDesktopIDELink(info *protocol.WorkspaceInfo, ide string, ownerToken string, t *testing.T) (string, error) { + var ( + ideLink string + err error + maxTries = 5 + ) + for i := 0; ideLink == "" && i < maxTries; i++ { + ideLink, err = fetchDekstopIDELink(info, ide, ownerToken) + if ideLink == "" && i < maxTries-1 { + t.Logf("failed to fetch IDE link: %v, trying again...", err) + } + } + if ideLink != "" { + return ideLink, nil + } + return "", err +} + +func fetchDekstopIDELink(info *protocol.WorkspaceInfo, ide string, ownerToken string) (string, error) { + body, err := getHttpContent(fmt.Sprintf("%s/_supervisor/v1/status/ide/wait/true", info.LatestInstance.IdeURL), ownerToken) + if err != nil { + return "", fmt.Errorf("failed to fetch IDE status response: %v", err) + } + + var ideStatus supervisor.IDEStatusResponse + err = json.Unmarshal(body, &ideStatus) + if err != nil { + return "", fmt.Errorf("failed to unmarshal IDE status response: %v, response body: %s", err, body) + } + if !ideStatus.GetOk() { + return "", fmt.Errorf("IDE status is not OK, response body: %s", body) + } + + desktop := ideStatus.GetDesktop() + if desktop == nil { + return "", fmt.Errorf("workspace does not have desktop IDE running, response body: %s", body) + } + if desktop.Kind != ide { + return "", fmt.Errorf("workspace does not have %s running, but %s, response body: %s", ide, desktop.Kind, body) + } + if desktop.Link == "" { + return "", fmt.Errorf("IDE link is empty, response body: %s", body) + } + return desktop.Link, nil +} + +func getHttpContent(url string, ownerToken string) ([]byte, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + req.Header.Set("x-gitpod-owner-token", ownerToken) + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + b, err := io.ReadAll(resp.Body) + return b, err +} + +type gatewayHostStatus struct { + AppPid int64 `json:"appPid"` + AppVersion string `json:"appVersion"` + IdePath string `json:"idePath"` + Projects []struct { + BackgroundTasksRunning bool `json:"backgroundTasksRunning"` + ControllerConnected bool `json:"controllerConnected"` + GatewayLink string `json:"gatewayLink"` + HTTPLink string `json:"httpLink"` + JoinLink string `json:"joinLink"` + ProjectName string `json:"projectName"` + ProjectPath string `json:"projectPath"` + SecondsSinceLastControllerActivity int64 `json:"secondsSinceLastControllerActivity"` + Users []string `json:"users"` + } `json:"projects"` + RuntimeVersion string `json:"runtimeVersion"` + UnattendedMode bool `json:"unattendedMode"` +} + +type JetBrainsTestCtx struct { + UserID string + SystemDir string +} + +func MustConnectToServer(ctx context.Context, t *testing.T, cfg *envconf.Config) (*integration.ComponentAPI, protocol.APIInterface, *integration.PAPIClient, string) { + t.Logf("connected to server") + api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) + t.Cleanup(func() { + api.Done(t) + }) + t.Logf("get or create user") + userID, err := api.CreateUser(username, userToken) + if err != nil { + t.Fatal(err) + } + + t.Logf("connecting to server...") + server, err := api.GitpodServer(integration.WithGitpodUser(username)) + if err != nil { + t.Fatal(err) + } + t.Logf("connecting to papi...") + papi, err := api.PublicApi(integration.WithGitpodUser(username)) + if err != nil { + t.Fatal(err) + } + return api, server, papi, userID +} diff --git a/test/tests/ide/jetbrains/gateway_test.go b/test/tests/ide/jetbrains/gateway_test.go index d9d454a86d6ff2..bf32e4ecfbacd5 100644 --- a/test/tests/ide/jetbrains/gateway_test.go +++ b/test/tests/ide/jetbrains/gateway_test.go @@ -4,48 +4,291 @@ package ide -import "testing" +import ( + "context" + _ "embed" + "fmt" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" + "github.com/gitpod-io/gitpod/test/pkg/integration" +) func TestGoLand(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + BaseGuard(t) + t.Parallel() + f := features.New("Start a workspace using GoLand"). + WithLabel("component", "IDE"). + WithLabel("ide", "GoLand"). + Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) + defer cancel() + JetBrainsIDETest(ctx, t, cfg, WithIDE("goland")) + return testCtx + }). + Feature() + testEnv.Test(t, f) } func TestIntellij(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + BaseGuard(t) + t.Parallel() + f := features.New("Start a workspace using Intellij"). + WithLabel("component", "IDE"). + WithLabel("ide", "Intellij"). + Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) + defer cancel() + + JetBrainsIDETest(ctx, t, cfg, WithIDE("intellij")) + return testCtx + }). + Feature() + testEnv.Test(t, f) } func TestPhpStorm(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + BaseGuard(t) + t.Parallel() + f := features.New("Start a workspace using PhpStorm"). + WithLabel("component", "IDE"). + WithLabel("ide", "PhpStorm"). + Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) + defer cancel() + JetBrainsIDETest(ctx, t, cfg, WithIDE("phpstorm")) + return testCtx + }). + Feature() + testEnv.Test(t, f) } func TestPyCharm(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + BaseGuard(t) + t.Parallel() + f := features.New("Start a workspace using Pycharm"). + WithLabel("component", "IDE"). + WithLabel("ide", "Pycharm"). + Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) + defer cancel() + JetBrainsIDETest(ctx, t, cfg, WithIDE("pycharm")) + return testCtx + }). + Feature() + testEnv.Test(t, f) } func TestRubyMine(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + BaseGuard(t) + t.Parallel() + f := features.New("Start a workspace using RubyMine"). + WithLabel("component", "IDE"). + WithLabel("ide", "RubyMine"). + Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) + defer cancel() + JetBrainsIDETest(ctx, t, cfg, WithIDE("rubymine")) + return testCtx + }). + Feature() + testEnv.Test(t, f) } func TestWebStorm(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + BaseGuard(t) + t.Parallel() + f := features.New("Start a workspace using WebStorm"). + WithLabel("component", "IDE"). + WithLabel("ide", "WebStorm"). + Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) + defer cancel() + JetBrainsIDETest(ctx, t, cfg, WithIDE("webstorm")) + return testCtx + }). + Feature() + testEnv.Test(t, f) } func TestRider(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + BaseGuard(t) + t.Parallel() + f := features.New("Start a workspace using Rider"). + WithLabel("component", "IDE"). + WithLabel("ide", "Rider"). + Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) + defer cancel() + JetBrainsIDETest(ctx, t, cfg, WithIDE("rider")) + return testCtx + }). + Feature() + testEnv.Test(t, f) } func TestCLion(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + BaseGuard(t) + t.Parallel() + t.Skip("See EXP-414") + f := features.New("Start a workspace using CLion"). + WithLabel("component", "IDE"). + WithLabel("ide", "CLion"). + Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) + defer cancel() + JetBrainsIDETest(ctx, t, cfg, WithIDE("clion")) + return testCtx + }). + Feature() + testEnv.Test(t, f) } func TestRustRover(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + BaseGuard(t) + t.Parallel() + f := features.New("Start a workspace using RustRover"). + WithLabel("component", "IDE"). + WithLabel("ide", "RustRover"). + Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) + defer cancel() + JetBrainsIDETest(ctx, t, cfg, WithIDE("rustrover")) + return testCtx + }). + Feature() + testEnv.Test(t, f) } func TestIntellijNotPreconfiguredRepo(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + BaseGuard(t) + t.Parallel() + f := features.New("Start a workspace using Intellij with not preconfigured repo"). + WithLabel("component", "IDE"). + WithLabel("ide", "Intellij"). + Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) + defer cancel() + // ENT-260 + // https://github.com/spring-projects/spring-petclinic is not an option because it will prompt to ask user to select project type + // which will block integration test (UI tests) + JetBrainsIDETest(ctx, t, cfg, WithIDE("intellij"), WithRepo("https://github.com/gitpod-io/empty")) + return testCtx + }). + Feature() + testEnv.Test(t, f) } +//go:embed warmup-indexing.sh +var warmupIndexingShell []byte + func TestIntelliJWarmup(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + BaseGuard(t) + t.Parallel() + f := features.New("Start a workspace using Intellij and imagebuild to test warmup tasks"). + WithLabel("component", "IDE"). + WithLabel("ide", "Intellij"). + Assess("it can let JetBrains Gateway connect", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx, cancel := context.WithTimeout(testCtx, 30*time.Minute) + defer cancel() + + testRepo := "https://github.com/gitpod-samples/spring-petclinic" + testRepoBranch := "gp/integration-test" + + api, _, papi, _ := MustConnectToServer(ctx, t, cfg) + t.Logf("get or create team") + teamID, err := api.GetTeam(ctx, papi) + if err != nil { + t.Fatalf("failed to get or create team: %v", err) + } + t.Logf("get or create repository for %s", testRepo) + projectID, err := api.GetProject(ctx, papi, teamID, "petclinic", testRepo, true) + if err != nil { + t.Fatalf("failed to get or create project: %v", err) + } + + triggerAndWaitForPrebuild := func() error { + prebuildID, err := api.TriggerPrebuild(ctx, papi, projectID, testRepoBranch) + if err != nil { + return fmt.Errorf("failed to trigger prebuild: %v", err) + } + t.Logf("prebuild triggered, id: %s", prebuildID) + ok, err := api.WaitForPrebuild(ctx, papi, prebuildID) + if err != nil { + return fmt.Errorf("failed to wait for prebuild: %v", err) + } + if !ok { + return fmt.Errorf("prebuild failed") + } + // EXP-1860 + // Prebuild is marked as available before content back-up is completed + if err := api.WaitForPrebuildWorkspaceToStoppedPhase(ctx, prebuildID); err != nil { + return fmt.Errorf("failed to wait for prebuild workspace to be backed-up : %v", err) + } + return nil + } + + t.Logf("trigger prebuild and wait for it") + if err := triggerAndWaitForPrebuild(); err != nil { + t.Fatalf("failed to trigger prebuild: %v", err) + } + t.Logf("prebuild available") + + t.Logf("warmup prebuild prepared, org: %s, repository: %s", teamID, projectID) + + JetBrainsIDETest(ctx, t, cfg, WithIDE("intellij"), + WithRepo(fmt.Sprintf("%s/tree/%s", testRepo, testRepoBranch)), + WithRepositoryID(projectID), + WithAdditionRpcCall(func(rsa *integration.RpcClient, jbCtx *JetBrainsTestCtx) error { + t.Logf("check if it has warmup.log") + var resp agent.ExecResponse + err := rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ + Dir: "/", + Command: "bash", + Args: []string{ + "-c", + fmt.Sprintf("stat %s/log/warmup/warmup.log", jbCtx.SystemDir), + }, + }, &resp) + if err != nil { + return fmt.Errorf("warmup.log not found: %v", err) + } + if resp.ExitCode != 0 { + return fmt.Errorf("warmup.log not found: %s, %d", resp.Stderr, resp.ExitCode) + } + return nil + }), + WithAdditionRpcCall(func(rsa *integration.RpcClient, jbCtx *JetBrainsTestCtx) error { + t.Logf("sleep for 1 minute to wait project open") + var resp agent.ExecResponse + time.Sleep(1 * time.Minute) + t.Logf("checking warmup indexing") + err := rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ + Dir: "/", + Command: "bash", + Args: []string{ + "-c", + string(warmupIndexingShell), + "--", + jbCtx.SystemDir, + "1", + }, + }, &resp) + if err != nil { + return fmt.Errorf("failed to warmup indexing: %v", err) + } + t.Logf("stdout:\n%s", string(resp.Stdout)) + if resp.ExitCode != 0 { + return fmt.Errorf("failed to warmup indexing: %s, %d", resp.Stderr, resp.ExitCode) + } + return nil + })) + return testCtx + }). + Feature() + testEnv.Test(t, f) } diff --git a/test/tests/ide/jetbrains/warmup-indexing.sh b/test/tests/ide/jetbrains/warmup-indexing.sh new file mode 100755 index 00000000000000..8bd7ee77ca7f70 --- /dev/null +++ b/test/tests/ide/jetbrains/warmup-indexing.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Copyright (c) 2024 Gitpod GmbH. All rights reserved. +# Licensed under the GNU Affero General Public License (AGPL). +# See License.AGPL.txt in the project root for license information. + +# This script is used to test JetBrains prebuild warmup indexing (search warmup-indexing.sh in codebase) +# It will get the last indexing json file (scan reason `On project open`) +# and check if the scheduled indexing count is greater than a specified threshold +# +# `exit 0` means JetBrains IDEs no need to indexing again +# Example: ./warmup-indexing.sh /workspace 1 + +set -euo pipefail +SystemDir=$1 +Threshold=$2 + +ProjectIndexingFolder=$(find "$SystemDir"/log/indexing-diagnostic -type d -name "spring*" -print -quit) +JsonFiles=$(find "$ProjectIndexingFolder" -type f -name "*.json") + +FilteredJsonFiles=() +for jsonFile in $JsonFiles; do + if jq -e '.projectIndexingActivityHistory.times.scanningReason == "On project open"' "$jsonFile" > /dev/null; then + FilteredJsonFiles+=("$jsonFile") + fi +done +mapfile -t sortedFiles < <(printf "%s\n" "${FilteredJsonFiles[@]}" | sort -r) + +targetFile=${sortedFiles[0]} +echo "Target indexing json file: $targetFile" +scheduledIndexing=$(jq '.projectIndexingActivityHistory.fileCount.numberOfFilesScheduledForIndexingAfterScan' "$targetFile") +echo "Scheduled indexing count: $scheduledIndexing, threshold: $Threshold" + +if [ "$scheduledIndexing" -gt "$Threshold" ]; then + echo "Error: Scheduled indexing count $scheduledIndexing > $Threshold" >&2 + exit 1 +else + exit 0 +fi diff --git a/test/tests/ide/ssh/ssh_gateway_test.go b/test/tests/ide/ssh/ssh_gateway_test.go index e902492059f7f5..6523b6df7e8b0f 100644 --- a/test/tests/ide/ssh/ssh_gateway_test.go +++ b/test/tests/ide/ssh/ssh_gateway_test.go @@ -4,8 +4,113 @@ package ide -import "testing" +import ( + "context" + "io" + "log" + "net/url" + "os" + "strings" + "testing" + "time" + + "github.com/helloyi/go-sshclient" + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + gitpod "github.com/gitpod-io/gitpod/gitpod-protocol" + "github.com/gitpod-io/gitpod/test/pkg/integration" +) func TestSSHGatewayConnection(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + userToken, _ := os.LookupEnv("USER_TOKEN") + integration.SkipWithoutUsername(t, username) + integration.SkipWithoutUserToken(t, userToken) + + f := features.New("TestSSHGatewayConnection"). + WithLabel("component", "server"). + Assess("it can connect to a workspace via SSH gateway", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx, cancel := context.WithTimeout(testCtx, 10*time.Minute) + defer cancel() + + api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) + t.Cleanup(func() { + api.Done(t) + }) + + _, err := api.CreateUser(username, userToken) + if err != nil { + t.Fatal(err) + } + + serverOpts := []integration.GitpodServerOpt{integration.WithGitpodUser(username)} + server, err := api.GitpodServer(serverOpts...) + if err != nil { + t.Fatal(err) + } + + // This env var caused an incident https://www.gitpodstatus.com/incidents/26gwnhcpvqqx before + // Which was introduced by PR https://github.com/gitpod-io/gitpod/pull/13822 + // And fixed by PR https://github.com/gitpod-io/gitpod/pull/13858 + _ = server.SetEnvVar(ctx, &gitpod.UserEnvVarValue{ + Name: "TEST", + RepositoryPattern: "*/*", + Value: "\\\"test space\\\"", + }) + + _ = server.SetEnvVar(ctx, &gitpod.UserEnvVarValue{ + Name: "TEST_MULTIPLE_LINES", + RepositoryPattern: "*/*", + Value: `Hello +World`, + }) + + nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, "github.com/gitpod-io/empty", username, api) + if err != nil { + t.Fatal(err) + } + defer func() { + sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer scancel() + + sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer sapi.Done(t) + + stopWs(true, sapi) + }() + + wsUrl, err := url.Parse(nfo.LatestInstance.IdeURL) + if err != nil { + t.Fatal(err) + } + + wId := nfo.Workspace.ID + ownerToken, err := server.GetOwnerToken(ctx, wId) + if err != nil { + t.Fatal(err) + } + + urlComponents := strings.Split(wsUrl.Host, ".") + connUrl := []string{urlComponents[0], "ssh"} + connUrl = append(connUrl, urlComponents[1:]...) + connUrlStr := strings.Join(connUrl, ".") + + cli, err := sshclient.DialWithPasswd(connUrlStr+":22", wId, ownerToken) + if err != nil { + t.Fatal(err) + } + + output, err := cli.Cmd("gp info").Output() + if err != nil && err != io.EOF { + log.Println("[error]", err) + time.Sleep(1 * time.Second) + } + + t.Log(string(output)) + + return testCtx + }). + Feature() + + testEnv.Test(t, f) } diff --git a/test/tests/ide/vscode/python_ws_test.go b/test/tests/ide/vscode/python_ws_test.go index d863867c3a18aa..c52637b46e87d5 100644 --- a/test/tests/ide/vscode/python_ws_test.go +++ b/test/tests/ide/vscode/python_ws_test.go @@ -4,8 +4,180 @@ package ide -import "testing" +import ( + "context" + "crypto/sha256" + "encoding/base64" + "errors" + "fmt" + "os" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + protocol "github.com/gitpod-io/gitpod/gitpod-protocol" + agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" + "github.com/gitpod-io/gitpod/test/pkg/integration" +) + +func poolTask(task func() (bool, error)) (bool, error) { + timeout := time.After(10 * time.Minute) + ticker := time.Tick(20 * time.Second) + for { + select { + case <-timeout: + return false, errors.New("timed out") + case <-ticker: + ok, err := task() + if err != nil { + return false, err + } else if ok { + return true, nil + } + } + } +} func TestPythonExtWorkspace(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + userToken, _ := os.LookupEnv("USER_TOKEN") + integration.SkipWithoutUsername(t, username) + integration.SkipWithoutUserToken(t, userToken) + + f := features.New("PythonExtensionWorkspace"). + WithLabel("component", "server"). + Assess("it can run python extension in a workspace", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx, cancel := context.WithTimeout(testCtx, 5*time.Minute) + defer cancel() + + api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) + t.Cleanup(func() { + api.Done(t) + }) + + userId, err := api.CreateUser(username, userToken) + if err != nil { + t.Fatal(err) + } + + serverOpts := []integration.GitpodServerOpt{integration.WithGitpodUser(username)} + server, err := api.GitpodServer(serverOpts...) + if err != nil { + t.Fatal(err) + } + + _, err = server.UpdateLoggedInUser(ctx, &protocol.User{ + AdditionalData: &protocol.AdditionalUserData{ + IdeSettings: &protocol.IDESettings{ + DefaultIde: "code-latest", + }, + }, + }) + if err != nil { + t.Fatalf("cannot set ide to vscode insiders: %q", err) + } + + nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, "github.com/gitpod-io/python-test-workspace", username, api) + if err != nil { + t.Fatal(err) + } + defer func() { + sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer scancel() + + sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer sapi.Done(t) + + stopWs(true, sapi) + }() + + _, err = integration.WaitForWorkspaceStart(t, ctx, nfo.LatestInstance.ID, nfo.Workspace.ID, api) + if err != nil { + t.Fatal(err) + } + + serverConfig, err := integration.GetServerConfig(cfg.Namespace(), cfg.Client()) + if err != nil { + t.Fatal(err) + } + + hash := sha256.Sum256([]byte(userId + serverConfig.Session.Secret)) + secretKey, err := api.CreateGitpodOneTimeSecret(fmt.Sprintf("%x", hash)) + if err != nil { + t.Fatal(err) + } + + sessionCookie, err := api.GitpodSessionCookie(userId, secretKey) + if err != nil { + t.Fatal(err) + } + + rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), integration.WithInstanceID(nfo.LatestInstance.ID), integration.WithWorkspacekitLift(true)) + if err != nil { + t.Fatal(err) + } + defer rsa.Close() + integration.DeferCloser(t, closer) + + _, err = poolTask(func() (bool, error) { + var resp agent.ExecResponse + err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ + Dir: "/workspace/python-test-workspace", + Command: "test", + Args: []string{ + "-f", + "__init_task_done__", + }, + }, &resp) + + return resp.ExitCode == 0, nil + }) + if err != nil { + t.Fatal(err) + } + + serverUrl, err := api.GetServerEndpoint() + + jsonCookie := fmt.Sprintf( + `{"name": "%v","value": "%v", "url": "%v","expires": %v,"httpOnly": %v,"secure": %v,"sameSite": "Lax"}`, + sessionCookie.Name, + sessionCookie.Value, + serverUrl, + sessionCookie.Expires.Unix(), + sessionCookie.HttpOnly, + sessionCookie.Secure, + ) + + var resp agent.ExecResponse + err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ + Dir: "/workspace/python-test-workspace", + Command: "yarn", + Args: []string{ + "gp-code-server-test", + fmt.Sprintf("--endpoint=%s", nfo.LatestInstance.IdeURL), + "--workspacePath=./src/testWorkspace", + "--extensionDevelopmentPath=./out", + "--extensionTestsPath=./out/test/suite", + }, + Env: []string{ + fmt.Sprintf("AUTH_COOKIE=%s", base64.StdEncoding.EncodeToString([]byte(jsonCookie))), + }, + }, &resp) + + if err != nil { + t.Fatal(err) + } + + t.Log("Ide integration stdout:\n", resp.Stdout) + if resp.ExitCode != 0 { + t.Log("Ide integration stderr:\n", resp.Stderr) + t.Fatal("There was an error running ide test") + } + + return testCtx + }). + Feature() + + testEnv.Test(t, f) } diff --git a/test/tests/smoke-test/smoke_test.go b/test/tests/smoke-test/smoke_test.go index e8e0b63949cfb6..3d8138eda9c25d 100644 --- a/test/tests/smoke-test/smoke_test.go +++ b/test/tests/smoke-test/smoke_test.go @@ -4,8 +4,53 @@ package smoketest -import "testing" +import ( + "context" + "os" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + "github.com/gitpod-io/gitpod/test/pkg/integration" +) func TestStartWorkspaceWithImageBuild(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + userToken, _ := os.LookupEnv("USER_TOKEN") + integration.SkipWithoutUsername(t, username) + integration.SkipWithoutUserToken(t, userToken) + + f := features.New("Start regular workspace"). + Assess("it can start a regular workspace with image build", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx, cancel := context.WithTimeout(testCtx, 5*time.Minute) + defer cancel() + + api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) + t.Cleanup(func() { + api.Done(t) + }) + + _, err := api.CreateUser(username, userToken) + if err != nil { + t.Fatal(err) + } + + _, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, "imagebuild/https://github.com/gitpod-integration-test/example", username, api) + if err != nil { + t.Fatal(err) + } + defer func() { + sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer scancel() + + sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer sapi.Done(t) + + _, _ = stopWs(true, sapi) + }() + return testCtx + }). + Feature() + testEnv.Test(t, f) } diff --git a/test/tests/workspace/contexts_test.go b/test/tests/workspace/contexts_test.go index b9e8dafd4d4b43..d37f362b369280 100644 --- a/test/tests/workspace/contexts_test.go +++ b/test/tests/workspace/contexts_test.go @@ -4,12 +4,193 @@ package workspace -import "testing" +import ( + "context" + "fmt" + "os" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + "github.com/gitpod-io/gitpod/test/pkg/integration" + "github.com/gitpod-io/gitpod/test/pkg/report" +) + +type ContextTest struct { + Skip bool + Name string + ContextURL string + WorkspaceRoot string + ExpectedBranch string + ExpectedBranchFunc func(username string) string + IgnoreError bool +} func TestGitHubContexts(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + tests := []ContextTest{ + { + Name: "open repository", + ContextURL: "github.com/gitpod-io/template-golang-cli", + WorkspaceRoot: "/workspace/template-golang-cli", + ExpectedBranch: "main", + }, + { + Name: "open branch", + ContextURL: "github.com/gitpod-io/gitpod-test-repo/tree/integration-test-1", + WorkspaceRoot: "/workspace/gitpod-test-repo", + ExpectedBranch: "integration-test-1", + }, + { + // Branch name decisions are not tested in the workspace as it is the server side logic + Name: "open issue", + ContextURL: "github.com/gitpod-io/gitpod-test-repo/issues/88", + WorkspaceRoot: "/workspace/gitpod-test-repo", + }, + { + Name: "open tag", + ContextURL: "github.com/gitpod-io/gitpod-test-repo/tree/integration-test-context-tag", + WorkspaceRoot: "/workspace/gitpod-test-repo", + ExpectedBranch: "HEAD", + }, + { + Name: "Git LFS support", + ContextURL: "github.com/atduarte/lfs-test", + WorkspaceRoot: "/workspace/lfs-test", + ExpectedBranch: "main", + }, + { + Name: "empty repo", + ContextURL: "github.com/gitpod-io/empty", + WorkspaceRoot: "/workspace/empty", + ExpectedBranch: "HEAD", + IgnoreError: true, + }, + } + runContextTests(t, tests) } func TestGitLabContexts(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + if !gitlab { + t.Skip("Skipping gitlab integration tests") + } + tests := []ContextTest{ + { + Name: "open repository", + ContextURL: "gitlab.com/AlexTugarev/gp-test", + WorkspaceRoot: "/workspace/gp-test", + ExpectedBranch: "master", + }, + { + Name: "open branch", + ContextURL: "gitlab.com/AlexTugarev/gp-test/tree/wip", + WorkspaceRoot: "/workspace/gp-test", + ExpectedBranch: "wip", + }, + { + Name: "open issue", + ContextURL: "gitlab.com/AlexTugarev/gp-test/issues/1", + WorkspaceRoot: "/workspace/gp-test", + }, + { + Name: "open tag", + ContextURL: "gitlab.com/AlexTugarev/gp-test/merge_requests/2", + WorkspaceRoot: "/workspace/gp-test", + ExpectedBranch: "wip2", + }, + } + runContextTests(t, tests) +} + +func runContextTests(t *testing.T, tests []ContextTest) { + userToken, _ := os.LookupEnv("USER_TOKEN") + integration.SkipWithoutUsername(t, username) + integration.SkipWithoutUserToken(t, userToken) + + f := features.New("context"). + WithLabel("component", "server"). + Assess("should run context tests", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + + sctx, scancel := context.WithTimeout(testCtx, time.Duration(10*len(tests))*time.Minute) + defer scancel() + + api := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer api.Done(t) + + for _, test := range tests { + test := test + t.Run(test.ContextURL, func(t *testing.T) { + report.SetupReport(t, report.FeatureContentInit, fmt.Sprintf("Test to open %v", test.ContextURL)) + if test.Skip { + t.SkipNow() + } + + t.Parallel() + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(5*len(tests))*time.Minute) + defer cancel() + + api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer api.Done(t) + + _, err := api.CreateUser(username, userToken) + if err != nil { + t.Fatal(err) + } + + nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, test.ContextURL, username, api) + if err != nil { + t.Fatal(err) + } + + t.Cleanup(func() { + sctx, scancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer scancel() + + sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer sapi.Done(t) + + _, err := stopWs(true, sapi) + if err != nil { + t.Fatal(err) + } + }) + + rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), integration.WithInstanceID(nfo.LatestInstance.ID)) + if err != nil { + t.Fatal(err) + } + defer rsa.Close() + integration.DeferCloser(t, closer) + + if test.ExpectedBranch == "" && test.ExpectedBranchFunc == nil { + return + } + + // get actual from workspace + git := integration.Git(rsa) + err = git.ConfigSafeDirectory() + if err != nil { + t.Fatal(err) + } + actBranch, err := git.GetBranch(test.WorkspaceRoot, test.IgnoreError) + if err != nil { + t.Fatal(err) + } + + expectedBranch := test.ExpectedBranch + if test.ExpectedBranchFunc != nil { + expectedBranch = test.ExpectedBranchFunc(username) + } + if actBranch != expectedBranch { + t.Fatalf("expected branch '%s', got '%s'!", expectedBranch, actBranch) + } + }) + } + return testCtx + }). + Feature() + + testEnv.Test(t, f) } diff --git a/test/tests/workspace/disk_test.go b/test/tests/workspace/disk_test.go index ab7386031e0dc6..c742f08e3ee773 100644 --- a/test/tests/workspace/disk_test.go +++ b/test/tests/workspace/disk_test.go @@ -4,8 +4,115 @@ package workspace -import "testing" +import ( + "context" + "fmt" + "strings" + + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + "github.com/gitpod-io/gitpod/test/pkg/integration" + "github.com/gitpod-io/gitpod/test/pkg/report" +) + +type DiskTest struct { + Name string + ContextURL string + SpaceToAllocate string + TestFilePath string + ExpectError bool +} func TestDiskActions(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + integration.SkipWithoutUsername(t, username) + + tests := []DiskTest{ + { + Name: "xfs-quota-is_exceeded", + ContextURL: "github.com/gitpod-io/empty", + SpaceToAllocate: "55G", + TestFilePath: "/workspace/is-exceeded", + ExpectError: true, + }, + { + Name: "xfs-quota-is_OK", + ContextURL: "github.com/gitpod-io/empty", + SpaceToAllocate: "4G", + TestFilePath: "/workspace/is-OK", + ExpectError: false, + }, + } + runDiskTests(t, tests) +} + +func runDiskTests(t *testing.T, tests []DiskTest) { + f := features.New("ResourceLimiting"). + WithLabel("component", "workspace"). + Assess("it can enforce disk limits", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + + ctx, cancel := context.WithTimeout(testCtx, time.Duration(5*len(tests))*time.Minute) + defer cancel() + + api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer api.Done(t) + + for _, test := range tests { + test := test + t.Run(test.Name, func(t *testing.T) { + report.SetupReport(t, report.FeatureResourceLimit, fmt.Sprintf("Test to open %v", test.ContextURL)) + + t.Parallel() + + nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, test.ContextURL, username, api) + if err != nil { + t.Fatal(err) + } + + t.Cleanup(func() { + sctx, scancel := context.WithTimeout(context.Background(), 10*time.Minute) + scancel() + + sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) + sapi.Done(t) + _, err := stopWs(false, sapi) + if err != nil { + t.Fatal(err) + } + }) + rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), + kubeconfig, cfg.Client(), + integration.WithInstanceID(nfo.LatestInstance.ID), + ) + if err != nil { + t.Fatal(err) + } + defer rsa.Close() + integration.DeferCloser(t, closer) + diskClient := integration.Disk(rsa) + + err = diskClient.Fallocate(test.TestFilePath, test.SpaceToAllocate) + + if test.ExpectError { + if err != nil && strings.Contains(err.Error(), integration.NoSpaceErrorMsg) { + // NOM + } else { + t.Fatalf("expected an error object containing %s, got '%v'!", integration.NoSpaceErrorMsg, err) + } + } else { + if err != nil { + t.Fatal(err) + } + } + t.Log("test finished successfully") + }) + } + return testCtx + }). + Feature() + + testEnv.Test(t, f) } diff --git a/test/tests/workspace/example_test.go b/test/tests/workspace/example_test.go index a85119eeab0aeb..39d8b9c562fa5a 100644 --- a/test/tests/workspace/example_test.go +++ b/test/tests/workspace/example_test.go @@ -6,17 +6,100 @@ package workspace import ( "context" + "os" "testing" "time" "sigs.k8s.io/e2e-framework/pkg/envconf" "sigs.k8s.io/e2e-framework/pkg/features" + agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" "github.com/gitpod-io/gitpod/test/pkg/integration" + "github.com/gitpod-io/gitpod/test/pkg/report" ) func TestWorkspaceInstrumentation(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + userToken, _ := os.LookupEnv("USER_TOKEN") + integration.SkipWithoutUsername(t, username) + integration.SkipWithoutUserToken(t, userToken) + tests := []struct { + Name string + ContextURL string + WorkspaceRoot string + }{ + { + Name: "example", + ContextURL: "https://github.com/gitpod-io/empty", + WorkspaceRoot: "/workspace/empty", + }, + } + + f := features.New("instrumentation"). + WithLabel("component", "server"). + Assess("it can instrument a workspace", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + for _, test := range tests { + test := test + t.Run(test.ContextURL, func(t *testing.T) { + report.SetupReport(t, report.FeatureExample, "this is the example test for instrumenting a workspace") + + t.Parallel() + + ctx, cancel := context.WithTimeout(testCtx, time.Duration(5*len(tests))*time.Minute) + defer cancel() + + api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) + t.Cleanup(func() { + api.Done(t) + }) + + _, err := api.CreateUser(username, userToken) + if err != nil { + t.Fatal(err) + } + + nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, test.ContextURL, username, api) + if err != nil { + t.Fatal(err) + } + + t.Cleanup(func() { + sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer scancel() + + sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer sapi.Done(t) + + _, err := stopWs(true, sapi) + if err != nil { + t.Fatal(err) + } + }) + + rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), integration.WithInstanceID(nfo.LatestInstance.ID)) + if err != nil { + t.Fatal(err) + } + defer rsa.Close() + integration.DeferCloser(t, closer) + + var ls agent.ListDirResponse + err = rsa.Call("WorkspaceAgent.ListDir", &agent.ListDirRequest{ + Dir: test.WorkspaceRoot, + }, &ls) + if err != nil { + t.Fatal(err) + } + for _, f := range ls.Files { + t.Log(f) + } + }) + } + + return testCtx + }). + Feature() + + testEnv.Test(t, f) } func TestLaunchWorkspaceDirectly(t *testing.T) { diff --git a/test/tests/workspace/git_hooks_test.go b/test/tests/workspace/git_hooks_test.go index 4b7cd8f047d069..931821b9c90532 100644 --- a/test/tests/workspace/git_hooks_test.go +++ b/test/tests/workspace/git_hooks_test.go @@ -4,8 +4,127 @@ package workspace -import "testing" +import ( + "context" + "os" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" + "github.com/gitpod-io/gitpod/test/pkg/integration" +) + +const ( + FILE_CREATED_HOOKS = "output.txt" +) + +type GitHooksTestCase struct { + Name string + ContextURL string + WorkspaceRoot string +} func TestGitHooks(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + userToken, _ := os.LookupEnv("USER_TOKEN") + integration.SkipWithoutUsername(t, username) + integration.SkipWithoutUserToken(t, userToken) + + tests := []GitHooksTestCase{ + { + Name: "husky", + ContextURL: "https://github.com/gitpod-io/gitpod-test-repo/tree/husky", + WorkspaceRoot: "/workspace/gitpod-test-repo", + }, + } + + f := features.New("git hooks"). + WithLabel("component", "server"). + Assess("should run git hooks tests", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ffs := []struct { + Name string + FF string + }{ + {Name: "classic"}, + } + + for _, ff := range ffs { + func() { + ctx, cancel := context.WithTimeout(testCtx, 10*time.Minute) + defer cancel() + + api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer api.Done(t) + + username := username + ff.Name + userId, err := api.CreateUser(username, userToken) + if err != nil { + t.Fatal(err) + } + + if err := api.UpdateUserFeatureFlag(userId, ff.FF); err != nil { + t.Fatal(err) + } + }() + } + + for _, ff := range ffs { + for _, test := range tests { + test := test + t.Run(test.ContextURL+"_"+ff.Name, func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(5*len(tests)*len(ffs))*time.Minute) + defer cancel() + + username := username + ff.Name + + api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer api.Done(t) + + wsInfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, test.ContextURL, username, api) + if err != nil { + t.Fatal(err) + } + + t.Cleanup(func() { + sctx, scancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer scancel() + + sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer sapi.Done(t) + + if _, err := stopWs(true, sapi); err != nil { + t.Fatal(err) + } + }) + rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), integration.WithInstanceID(wsInfo.LatestInstance.ID)) + if err != nil { + t.Fatal(err) + } + defer rsa.Close() + integration.DeferCloser(t, closer) + + var ls agent.ListDirResponse + err = rsa.Call("WorkspaceAgent.ListDir", &agent.ListDirRequest{ + Dir: test.WorkspaceRoot, + }, &ls) + if err != nil { + t.Fatal(err) + } + for _, f := range ls.Files { + if f == FILE_CREATED_HOOKS { + t.Fatal("Checkout hooks are executed") + } + } + }) + } + } + return testCtx + }). + Feature() + + testEnv.Test(t, f) } diff --git a/test/tests/workspace/git_test.go b/test/tests/workspace/git_test.go index 63cbb48975b996..e7c4695c66cdf5 100644 --- a/test/tests/workspace/git_test.go +++ b/test/tests/workspace/git_test.go @@ -4,8 +4,204 @@ package workspace -import "testing" +import ( + "context" + "fmt" + "os" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" + "github.com/gitpod-io/gitpod/test/pkg/integration" +) + +type GitTest struct { + Skip bool + Name string + ContextURL string + WorkspaceRoot string + Action GitFunc +} + +type GitFunc func(rsa *integration.RpcClient, git integration.GitClient, workspaceRoot string) error func TestGitActions(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + userToken, _ := os.LookupEnv("USER_TOKEN") + integration.SkipWithoutUsername(t, username) + integration.SkipWithoutUserToken(t, userToken) + + tests := []GitTest{ + { + Name: "create, add and commit", + ContextURL: "github.com/gitpod-io/gitpod-test-repo/tree/integration-test/commit", + WorkspaceRoot: "/workspace/gitpod-test-repo", + Action: func(rsa *integration.RpcClient, git integration.GitClient, workspaceRoot string) (err error) { + var resp agent.ExecResponse + err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ + Dir: workspaceRoot, + Command: "bash", + Args: []string{ + "-c", + "touch file_to_commit.txt", + }, + }, &resp) + if err != nil { + return err + } + if resp.ExitCode != 0 { + return fmt.Errorf("file create returned rc: %d, out: %v, err: %v", resp.ExitCode, resp.Stdout, resp.Stderr) + } + err = git.ConfigSafeDirectory() + if err != nil { + return err + } + err = git.ConfigUserName(workspaceRoot, username) + if err != nil { + return err + } + err = git.ConfigUserEmail(workspaceRoot) + if err != nil { + return err + } + err = git.Add(workspaceRoot) + if err != nil { + return err + } + err = git.Commit(workspaceRoot, "automatic test commit", false, "--allow-empty") + if err != nil { + return err + } + return nil + }, + }, + { + // as of Apr 14, 2023, test fails with: + // fatal: could not read Username for 'https://github.com': No such device or address + Skip: true, + Name: "create, add and commit and PUSH", + ContextURL: "github.com/gitpod-io/gitpod-test-repo/tree/integration-test/commit-and-push", + WorkspaceRoot: "/workspace/gitpod-test-repo", + Action: func(rsa *integration.RpcClient, git integration.GitClient, workspaceRoot string) (err error) { + var resp agent.ExecResponse + err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ + Dir: workspaceRoot, + Command: "bash", + Args: []string{ + "-c", + "touch file_to_commit.txt", + }, + }, &resp) + if err != nil { + return err + } + if resp.ExitCode != 0 { + return fmt.Errorf("file create returned rc: %d, out: %v, err: %v", resp.ExitCode, resp.Stdout, resp.Stderr) + } + err = git.ConfigSafeDirectory() + if err != nil { + return err + } + err = git.ConfigUserName(workspaceRoot, username) + if err != nil { + return err + } + err = git.ConfigUserEmail(workspaceRoot) + if err != nil { + return err + } + err = git.Add(workspaceRoot) + if err != nil { + return err + } + err = git.Commit(workspaceRoot, "automatic test commit", false, "--allow-empty") + if err != nil { + return err + } + err = git.Push(workspaceRoot, false) + if err != nil { + return err + } + return nil + }, + }, + } + + f := features.New("GitActions"). + WithLabel("component", "workspace"). + Assess("it can run git actions", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx, cancel := context.WithTimeout(testCtx, time.Duration(5*len(tests))*time.Minute) + defer cancel() + + api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) + t.Cleanup(func() { + api.Done(t) + }) + + ffs := []struct { + Name string + FF string + }{ + {Name: "classic"}, + } + + for _, ff := range ffs { + for _, test := range tests { + test := test + t.Run(test.ContextURL+"_"+ff.Name, func(t *testing.T) { + t.Parallel() + if test.Skip { + t.SkipNow() + } + + username := username + ff.Name + userId, err := api.CreateUser(username, userToken) + if err != nil { + t.Fatal(err) + } + + if err := api.UpdateUserFeatureFlag(userId, ff.FF); err != nil { + t.Fatal(err) + } + + nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, test.ContextURL, username, api) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer scancel() + + sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer sapi.Done(t) + + _, err := stopWs(false, sapi) + if err != nil { + t.Fatal(err) + } + }) + + rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), integration.WithInstanceID(nfo.LatestInstance.ID)) + if err != nil { + t.Fatal(err) + } + defer rsa.Close() + integration.DeferCloser(t, closer) + + git := integration.Git(rsa) + err = test.Action(rsa, git, test.WorkspaceRoot) + if err != nil { + t.Fatal(err) + } + t.Log("test finished successfully") + }) + } + } + return testCtx + }). + Feature() + + testEnv.Test(t, f) } diff --git a/test/tests/workspace/ports_test.go b/test/tests/workspace/ports_test.go index 1b572a13be216b..a4c4e52c8eff12 100644 --- a/test/tests/workspace/ports_test.go +++ b/test/tests/workspace/ports_test.go @@ -4,8 +4,213 @@ package workspace -import "testing" +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "os" + "reflect" + "strings" + "testing" + "time" + + gitpod "github.com/gitpod-io/gitpod/gitpod-protocol" + agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" + "github.com/gitpod-io/gitpod/test/pkg/integration" + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" +) func TestRegularWorkspacePorts(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + userToken, _ := os.LookupEnv("USER_TOKEN") + integration.SkipWithoutUsername(t, username) + integration.SkipWithoutUserToken(t, userToken) + + // This branch exposes a python server on port 3000 as part of the Gitpod tasks. + testRepo := "https://github.com/gitpod-io/gitpod-test-repo/tree/integration-test/ports" + testRepoName := "gitpod-test-repo" + wsLoc := fmt.Sprintf("/workspace/%s", testRepoName) + + f := features.New("ports"). + WithLabel("component", "workspace"). + WithLabel("type", "ports"). + Assess("it can open and access workspace ports", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + t.Parallel() + + ctx, cancel := context.WithTimeout(testCtx, time.Duration(5*time.Minute)) + defer cancel() + + api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) + t.Cleanup(func() { + api.Done(t) + }) + + _, err := api.CreateUser(username, userToken) + if err != nil { + t.Fatal(err) + } + + serverOpts := []integration.GitpodServerOpt{integration.WithGitpodUser(username)} + server, err := api.GitpodServer(serverOpts...) + if err != nil { + t.Fatal(err) + } + + // Must change supervisor address from localhost to 10.0.5.2. + err = server.SetEnvVar(ctx, &gitpod.UserEnvVarValue{ + Name: "SUPERVISOR_ADDR", + Value: `10.0.5.2:22999`, + RepositoryPattern: "gitpod-io/" + testRepoName, + }) + if err != nil { + t.Fatal(err) + } + defer func() { + err := server.DeleteEnvVar(ctx, &gitpod.UserEnvVarValue{ + Name: "SUPERVISOR_ADDR", + RepositoryPattern: "gitpod-io/" + testRepoName, + }) + if err != nil { + t.Fatal(err) + } + }() + + nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, testRepo, username, api, integration.WithGitpodUser(username)) + if err != nil { + t.Fatal(err) + } + + t.Cleanup(func() { + sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer scancel() + + sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer sapi.Done(t) + + if _, err = stopWs(true, sapi); err != nil { + t.Errorf("cannot stop workspace: %q", err) + } + }) + + instanceId := nfo.LatestInstance.ID + rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), integration.WithInstanceID(instanceId)) + integration.DeferCloser(t, closer) + if err != nil { + t.Fatalf("unexpected error instrumenting workspace: %v", err) + } + defer rsa.Close() + + type Port struct { + LocalPort int `json:"localPort,omitempty"` + Exposed struct { + Visibility string `json:"visibility,omitempty"` + Url string `json:"url,omitempty"` + } `json:"exposed,omitempty"` + } + var portsResp struct { + Result struct { + Ports []*Port `json:"ports,omitempty"` + } `json:"result"` + } + + expectPort := func(expected Port) { + for i := 0; i < 10; i++ { + var res agent.ExecResponse + err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ + Dir: wsLoc, + Command: "curl", + // nftable rule only forwards to this ip address + Args: []string{"10.0.5.2:22999/_supervisor/v1/status/ports"}, + }, &res) + if err != nil { + t.Fatal(err) + } + err = json.Unmarshal([]byte(res.Stdout), &portsResp) + if err != nil { + t.Fatalf("cannot decode supervisor ports status response: %s", err) + } + + t.Logf("ports: %s (%d)", res.Stdout, res.ExitCode) + if len(portsResp.Result.Ports) != 1 { + t.Logf("expected one port to be open, but got %d, retrying", len(portsResp.Result.Ports)) + time.Sleep(2 * time.Second) + continue + } + if !reflect.DeepEqual(expected, *portsResp.Result.Ports[0]) { + t.Logf("expected %v but got %v, retrying", expected, *portsResp.Result.Ports[0]) + time.Sleep(2 * time.Second) + continue + } + + // Got expected port. + return + } + + // If we get here, we didn't get the expected port status after retrying. + t.Fatalf("did not get expected port status after 10 attempts") + } + + t.Logf("checking that port has been auto-detected, and is private") + portUrl := fmt.Sprintf("https://%d-%s", 3000, strings.TrimPrefix(nfo.LatestInstance.IdeURL, "https://")) + expectPort(Port{ + LocalPort: 3000, + Exposed: struct { + Visibility string `json:"visibility,omitempty"` + Url string `json:"url,omitempty"` + }{ + Visibility: "private", + Url: portUrl, + }, + }) + + t.Logf("checking that private port is not accessible from outside the workspace at %s", portUrl) + res, err := http.Get(portUrl) + if err != nil { + t.Fatal(err) + } + if res.StatusCode != http.StatusUnauthorized { + t.Fatalf("expected status code 401, but got %d", res.StatusCode) + } + + // Make port public. + t.Logf("making port public via gp ports visibility") + var res1 agent.ExecResponse + err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ + Dir: wsLoc, + Command: "gp", + // nftable rule only forwards to this ip address + Args: []string{"ports", "visibility", "3000:public"}, + }, &res1) + if err != nil { + t.Fatal(err) + } + t.Logf("debug: gp CLI output: %s, %s, %d", res1.Stdout, res1.Stderr, res1.ExitCode) + + t.Logf("checking that port is now public") + expectPort(Port{ + LocalPort: 3000, + Exposed: struct { + Visibility string `json:"visibility,omitempty"` + Url string `json:"url,omitempty"` + }{ + Visibility: "public", + Url: portUrl, + }, + }) + + t.Logf("checking if port is accessible from outside the workspace at %s", portUrl) + res, err = http.Get(portUrl) + if err != nil { + t.Fatal(err) + } + if res.StatusCode != http.StatusOK { + t.Fatalf("expected status code 200, but got %d", res.StatusCode) + } + + return testCtx + }). + Feature() + + testEnv.Test(t, f) } diff --git a/test/tests/workspace/process_priority_test.go b/test/tests/workspace/process_priority_test.go index a44065a750fd34..7e00da25f52975 100644 --- a/test/tests/workspace/process_priority_test.go +++ b/test/tests/workspace/process_priority_test.go @@ -4,8 +4,140 @@ package workspace -import "testing" +import ( + "context" + "fmt" + "os" + "strconv" + "strings" + "testing" + "time" + + agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api" + "github.com/gitpod-io/gitpod/test/pkg/integration" + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" +) func TestProcessPriority(t *testing.T) { - t.Skip("Disabled: GitHub user-token integration coverage has been removed; see test/README.md.") + userToken, _ := os.LookupEnv("USER_TOKEN") + integration.SkipWithoutUsername(t, username) + integration.SkipWithoutUserToken(t, userToken) + + f := features.New("process priority"). + WithLabel("component", "workspace"). + WithLabel("type", "process priority"). + Assess("it has set process priority", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + t.Parallel() + + ctx, cancel := context.WithTimeout(testCtx, time.Duration(5*time.Minute)) + defer cancel() + + api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) + t.Cleanup(func() { + api.Done(t) + }) + + _, err := api.CreateUser(username, userToken) + if err != nil { + t.Fatal(err) + } + + nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(t, ctx, "https://github.com/gitpod-io/empty", username, api, integration.WithGitpodUser(username)) + if err != nil { + t.Fatal(err) + } + + t.Cleanup(func() { + sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer scancel() + + sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client()) + defer sapi.Done(t) + + if _, err = stopWs(true, sapi); err != nil { + t.Errorf("cannot stop workspace: %v", err) + } + }) + + rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(), integration.WithInstanceID(nfo.LatestInstance.ID)) + integration.DeferCloser(t, closer) + if err != nil { + t.Fatalf("unexpected error instrumenting workspace: %v", err) + } + defer rsa.Close() + + t.Logf("waiting for the next ws-daemon tick, before running ps") + time.Sleep(15 * time.Second) + + var res agent.ExecResponse + err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{ + Dir: "/workspace", + Command: "ps", + Args: []string{"eax", "-o", "ni,cmd", "--no-headers"}, + }, &res) + if err != nil { + t.Fatal(err) + } + if res.ExitCode != 0 { + t.Fatalf("ps failed (%d): %s", res.ExitCode, res.Stderr) + } + + checkProcessPriorities(t, res.Stdout) + + return testCtx + }). + Feature() + + testEnv.Test(t, f) +} + +func checkProcessPriorities(t *testing.T, output string) { + t.Helper() + + processes := strings.Split(output, "\n") + for _, p := range processes { + parts := strings.Fields(p) + if len(parts) >= 2 { + checkProcessPriority(t, parts[0], parts[1]) + } + } +} + +func checkProcessPriority(t *testing.T, priority, process string) { + t.Helper() + + actualPrio, err := strconv.Atoi(priority) + if err != nil { + return + } + + expectedPrio, err := determinePriority(process) + if err != nil { + return + } + + if actualPrio != expectedPrio { + t.Fatalf("expected priority of %v for process %v, but was %v", expectedPrio, process, actualPrio) + } +} + +func determinePriority(process string) (int, error) { + if strings.HasSuffix(process, "supervisor") { + return -10, nil + } + + if strings.HasSuffix(process, "/bin/code-server") { + return -10, nil + } + + if strings.HasSuffix(process, "/ide/bin/gitpod-code") { + return -10, nil + } + + if strings.HasSuffix(process, "/ide/node") { + return -5, nil + } + + return 0, fmt.Errorf("unknown") } From 84cabdac3075e2ea113b65698dfe7e58ebd31ff8 Mon Sep 17 00:00:00 2001 From: Kyle Brennan Date: Thu, 1 Oct 2026 19:28:53 +0000 Subject: [PATCH 4/4] Check stderr for process-limit fork diagnostics Co-authored-by: Codex --- test/tests/workspace/process_limit_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/tests/workspace/process_limit_test.go b/test/tests/workspace/process_limit_test.go index 251019de9d07ac..e3b8c441f968bf 100644 --- a/test/tests/workspace/process_limit_test.go +++ b/test/tests/workspace/process_limit_test.go @@ -103,8 +103,9 @@ func TestProcessLimit(t *testing.T) { } t.Logf("checking output for fork errors due to process limiting") - if !strings.Contains(res.Stdout, "bash: fork: retry: Resource temporarily unavailable") { - t.Errorf("expected fork error (Resource temporarily unavailable), but got none (%d): %s", res.ExitCode, res.Stdout) + // Exec captures stderr separately, including bash's fork diagnostics. + if !strings.Contains(res.Stdout+res.Stderr, "bash: fork: retry: Resource temporarily unavailable") { + t.Errorf("expected fork error (Resource temporarily unavailable), but got none (%d): stdout: %s\nstderr: %s", res.ExitCode, res.Stdout, res.Stderr) } return testCtx