From 2700cdb34bc6aa1389b46c78954c1f1c5201bd50 Mon Sep 17 00:00:00 2001 From: Martin Entlicher Date: Thu, 16 Nov 2023 12:48:04 +0100 Subject: [PATCH] Inspector integration tests with CDT UI. --- .github/workflows/cdt-inspect.yml | 71 +++++ .github/workflows/main.yml | 187 ------------- .github/workflows/quarkus.yml | 175 ------------ .gitignore | 1 + .../gh_workflows/CDTInspectorTest/pom.xml | 36 +++ .../CDTInspectorTest/scripts/StepTest.js | 32 +++ .../CDTInspectorTest/scripts/StepTest.js.out | 85 ++++++ .../workflowtest/AbstractTest.java | 60 +++++ .../chromeinspector/workflowtest/CDTLib.java | 248 ++++++++++++++++++ .../workflowtest/Launcher.java | 89 +++++++ .../chromeinspector/workflowtest/Test.java | 105 ++++++++ .../workflowtest/TestCDTBasics.java | 135 ++++++++++ 12 files changed, 862 insertions(+), 362 deletions(-) create mode 100644 .github/workflows/cdt-inspect.yml delete mode 100644 .github/workflows/main.yml delete mode 100644 .github/workflows/quarkus.yml create mode 100644 vm/tests/gh_workflows/CDTInspectorTest/pom.xml create mode 100644 vm/tests/gh_workflows/CDTInspectorTest/scripts/StepTest.js create mode 100644 vm/tests/gh_workflows/CDTInspectorTest/scripts/StepTest.js.out create mode 100644 vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/AbstractTest.java create mode 100644 vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/CDTLib.java create mode 100644 vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/Launcher.java create mode 100644 vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/Test.java create mode 100644 vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/TestCDTBasics.java diff --git a/.github/workflows/cdt-inspect.yml b/.github/workflows/cdt-inspect.yml new file mode 100644 index 0000000000000..a4913bdba501a --- /dev/null +++ b/.github/workflows/cdt-inspect.yml @@ -0,0 +1,71 @@ +# Intergation test of CDT with Inspector backend. +name: Weekly CDT Inspector + +on: + schedule: + # - cron: "30 2 * * 5" # Friday at 2:30 + - cron: "45 2 * * *" + +env: + JAVA_HOME: ${{ github.workspace }}/jdk + JDK_VERSION: "latest" + MX_PATH: ${{ github.workspace }}/mx + SE_SKIP_DRIVER_IN_PATH: "true" + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: browser-actions/setup-chrome@v1 + with: + chrome-version: dev + id: setup-chrome + - name: Checkout oracle/graal + uses: actions/checkout@v4 + with: + fetch-depth: 1 + path: ${{ github.workspace }}/graal + - run: | + ls -d ${{ github.workspace }} + ls -l ${{ github.workspace }} + - name: Checkout oracle/graaljs + uses: actions/checkout@v4 + with: + repository: oracle/graaljs + fetch-depth: 1 + sparse-checkout: | + graal-js + path: ${{ github.workspace }}/js + - run: | + ls -l ${{ github.workspace }} + ls -l ${{ github.workspace }}/* + - name: Checkout graalvm/mx + uses: actions/checkout@v4 + with: + repository: graalvm/mx.git + fetch-depth: 1 + ref: master + path: ${{ env.MX_PATH }} + - run: | + ls -l ${{ github.workspace }} + ls -l ${{ github.workspace }}/* + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.8' + - name: Fetch LabsJDK + run: | + mkdir jdk-dl + ${MX_PATH}/mx --java-home= fetch-jdk --jdk-id labsjdk-ce-${JDK_VERSION} --to jdk-dl --alias ${JAVA_HOME} + - run: | + echo Installed chromium version: ${{ steps.setup-chrome.outputs.chrome-version }} + ${{ steps.setup-chrome.outputs.chrome-path }} --version + ls -l ${{ github.workspace }} + ls -l ${{ github.workspace }}/* + cd ${{ github.workspace }}/graal/vm + ${MX_PATH}/mx --dy /tools,graal-js build + cd tests/gh_workflows/CDTInspectorTest + mvn -q compile + mvn -q exec:java -Dexec.args="${{ github.workspace }}/graal/sdk/latest_graalvm_home/bin/js scripts/StepTest.js ${{ steps.setup-chrome.outputs.chrome-path }} ${{ steps.setup-chrome.outputs.chrome-version }}" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index d870475496172..0000000000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,187 +0,0 @@ -name: GraalVM Gate - -on: - push: - branches: - - 'master' - - 'release/**' - paths-ignore: - - '.devcontainer/**' - - '.github/workflows/quarkus.yml' - - '**.md' - - '**.jsonnet' - - '**.libjsonnet' - pull_request: - paths-ignore: - - '.devcontainer/**' - - '.github/workflows/quarkus.yml' - - '**.md' - - '**.jsonnet' - - '**.libjsonnet' - # Enable manual dispatch of the workflow - # see https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow - workflow_dispatch: - -# The following aims to reduce CI CPU cycles by: -# 1. Cancelling any previous builds of this PR when pushing new changes to it -# 2. Cancelling any previous builds of a branch when pushing new changes to it in a fork -# 3. Cancelling any pending builds, but not active ones, when pushing to a branch in the main -# repository. This prevents us from constantly cancelling CI runs, while being able to skip -# intermediate builds. E.g., if we perform two pushes the first one will start a CI job and -# the second one will add another one to the queue; if we perform a third push while the -# first CI job is still running the previously queued CI job (for the second push) will be -# cancelled and a new CI job will be queued for the latest (third) push. -concurrency: - group: "workflow = ${{ github.workflow }}, ref = ${{ github.event.ref }}, pr = ${{ github.event.pull_request.id }}" - cancel-in-progress: ${{ github.event_name == 'pull_request' || github.repository != 'oracle/graal' }} - -env: - JAVA_HOME: ${{ github.workspace }}/jdk - JDT: builtin - LANG: en_US.UTF-8 - MX_GIT_CACHE: refcache - MX_PATH: ${{ github.workspace }}/mx - MX_PYTHON: python3.8 - # Enforce experimental option checking in CI (GR-47922) - NATIVE_IMAGE_EXPERIMENTAL_OPTIONS_ARE_FATAL: "true" - -permissions: - contents: read # to fetch code (actions/checkout) - -jobs: - build-graalvm: - name: /${{ matrix.env.PRIMARY }} ${{ matrix.env.GATE_TAGS }} JDK${{ matrix.env.JDK_VERSION }} - runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - include: - # /compiler - - env: - JDK_VERSION: "21" - GATE_TAGS: "style,fullbuild,test" - PRIMARY: "compiler" - - env: - JDK_VERSION: "latest" - GATE_TAGS: "build,bootstraplite" - PRIMARY: "compiler" - # /espresso - - env: - JDK_VERSION: "21" - GATE_TAGS: "style,fullbuild" - PRIMARY: "espresso" - # /substratevm - - env: - JDK_VERSION: "21" - GATE_TAGS: "style,fullbuild" - PRIMARY: "substratevm" - - env: - JDK_VERSION: "latest" - GATE_TAGS: "build,helloworld,native_unittests" - PRIMARY: "substratevm" - PIP_PACKAGES: "jsonschema==4.6.1" - - env: - JDK_VERSION: "latest" - GATE_TAGS: "build,debuginfotest" - PRIMARY: "substratevm" - - env: - JDK_VERSION: "latest" - GATE_TAGS: "hellomodule" - PRIMARY: "substratevm" - # /sulong - - env: - JDK_VERSION: "21" - GATE_TAGS: "style,fullbuild,sulongBasic" - PRIMARY: "sulong" - # /truffle - - env: - JDK_VERSION: "21" - GATE_TAGS: "" # Truffle does not use tags - PRIMARY: "truffle" - # /vm - - env: - JDK_VERSION: "latest" - GATE_TAGS: "build,sulong" - GATE_OPTS: "--no-warning-as-error" - PRIMARY: "vm" - DYNAMIC_IMPORTS: "/sulong,/substratevm" - NATIVE_IMAGES: "graalvm-native-binutil,graalvm-native-clang,graalvm-native-clang-cl,graalvm-native-clang++,graalvm-native-ld,lib:llvmvm" - DISABLE_POLYGLOT: true - DISABLE_LIBPOLYGLOT: true - - env: - JDK_VERSION: "latest" - GATE_TAGS: "build" - GATE_OPTS: "--no-warning-as-error" - PRIMARY: "vm" - DYNAMIC_IMPORTS: "/tools,/substratevm,/sulong" - NATIVE_IMAGES: "lib:jvmcicompiler,native-image,lib:native-image-agent,lib:native-image-diagnostics-agent,polyglot" - WITHOUT_VCS: true - env: - MX_RUNS_DEBUG: ${{ contains(matrix.env.GATE_TAGS, 'debug') || matrix.env.GATE_TAGS == '' }} - MX_RUNS_STYLE: ${{ contains(matrix.env.GATE_TAGS, 'style') || matrix.env.GATE_TAGS == '' }} - steps: - - name: Checkout oracle/graal - uses: actions/checkout@v3 - with: - ref: ${{ github.ref }} # Lock ref to current branch to avoid fetching others - fetch-depth: "${{ env.MX_RUNS_STYLE && '0' || '1' }}" # The style gate needs the full commit history for checking copyright years - - name: Determine mx version - run: echo "MX_VERSION=$(jq -r '.mx_version' common.json)" >> ${GITHUB_ENV} - - name: Checkout graalvm/mx - uses: actions/checkout@v3 - with: - repository: graalvm/mx.git - ref: ${{ env.MX_VERSION }} - fetch-depth: 1 - path: ${{ env.MX_PATH }} - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.8' - - name: Update mx cache - uses: actions/cache@v3 - with: - path: ~/.mx - key: ${{ runner.os }}-mx-${{ hashFiles('**/suite.py') }} - restore-keys: ${{ runner.os }}-mx- - - name: Fetch LabsJDK - env: ${{ matrix.env }} - run: | - mkdir jdk-dl - ${MX_PATH}/mx --java-home= fetch-jdk --jdk-id labsjdk-ce-${JDK_VERSION} --to jdk-dl --alias ${JAVA_HOME} - - name: Update dependency cache - if: ${{ env.MX_RUNS_DEBUG == 'true' || env.MX_RUNS_STYLE == 'true' }} - run: sudo apt update - - name: Install debug dependencies - if: ${{ env.MX_RUNS_DEBUG == 'true' }} - run: sudo apt install gdb - - name: Install style dependencies - if: ${{ env.MX_RUNS_STYLE == 'true' }} - run: | - sudo apt install python3-pip python-setuptools - sudo pip install ninja_syntax$(jq -r '.pip.ninja_syntax' common.json) - sudo pip install lazy-object-proxy$(jq -r '.pip["lazy-object-proxy"]' common.json) - sudo pip install pylint$(jq -r '.pip.pylint' common.json) - - name: Install additional pip packages - if: ${{ matrix.env.PIP_PACKAGES != '' }} - run: ${MX_PYTHON} -m pip install ${{ matrix.env.PIP_PACKAGES }} - - name: Download Eclipse - if: ${{ env.MX_RUNS_STYLE == 'true' }} - run: | - ECLIPSE_TAR=eclipse.tar.gz - ECLIPSE_ORG_VERSION=$(jq -r '.eclipse.short_version' common.json) - ECLIPSE_ORG_TIMESTAMP=$(jq -r '.eclipse.timestamp' common.json) - wget --no-verbose https://archive.eclipse.org/eclipse/downloads/drops4/R-${ECLIPSE_ORG_VERSION}-${ECLIPSE_ORG_TIMESTAMP}/eclipse-SDK-${ECLIPSE_ORG_VERSION}-linux-gtk-x86_64.tar.gz -O $ECLIPSE_TAR - tar -xzf ${ECLIPSE_TAR} - echo "ECLIPSE_EXE=${PWD}/eclipse/eclipse" >> $GITHUB_ENV - - name: Remove .git directory - if: ${{ matrix.env.WITHOUT_VCS }} - run: rm -rf .git - - name: Build GraalVM and run gate with tags - env: ${{ matrix.env }} - run: ${MX_PATH}/mx --primary-suite-path ${PRIMARY} --java-home=${JAVA_HOME} gate --strict-mode ${{ matrix.env.GATE_OPTS }} --tags ${GATE_TAGS} - if: ${{ matrix.env.GATE_TAGS != '' }} - - name: Build GraalVM and run gate without tags - env: ${{ matrix.env }} - run: ${MX_PATH}/mx --primary-suite-path ${PRIMARY} --java-home=${JAVA_HOME} gate --strict-mode ${{ matrix.env.GATE_OPTS }} - if: ${{ matrix.env.GATE_TAGS == '' }} diff --git a/.github/workflows/quarkus.yml b/.github/workflows/quarkus.yml deleted file mode 100644 index 5a92174e957c0..0000000000000 --- a/.github/workflows/quarkus.yml +++ /dev/null @@ -1,175 +0,0 @@ -name: Nightly Quarkus Tests - -on: - push: - paths: - - '.github/workflows/quarkus.yml' - pull_request: - paths: - - '.github/workflows/quarkus.yml' - schedule: - - cron: '0 3 * * *' - -env: - COMMON_MAVEN_ARGS: "-e -B --settings .github/mvn-settings.xml --fail-at-end" - DB_NAME: hibernate_orm_test - DB_PASSWORD: hibernate_orm_test - DB_USER: hibernate_orm_test - GRAALVM_HOME: ${{ github.workspace }}/graalvm - LABSJDK_HOME: ${{ github.workspace }}/jdk - LANG: en_US.UTF-8 # Workaround testsuite locale issue - MX_GIT_CACHE: refcache - MX_PATH: ${{ github.workspace }}/mx - MX_PYTHON: python3.8 - NATIVE_TEST_MAVEN_ARGS: "-Dtest-containers -Dstart-containers -Dquarkus.native.native-image-xmx=5g -Dnative -Dnative.surefire.skip -Dformat.skip -Dno-descriptor-tests install -DskipDocs -Dquarkus.native.container-build=false" - QUARKUS_PATH: ${{ github.workspace }}/quarkus - -permissions: {} -jobs: - build-quarkus-and-graalvm: - permissions: - contents: read # to fetch code (actions/checkout) - - name: Nightly Quarkus and GraalVM build - runs-on: ubuntu-20.04 - outputs: - matrix: ${{ steps.read.outputs.matrix }} - steps: - - name: Checkout oracle/graal - uses: actions/checkout@v3 - with: - fetch-depth: 1 - - name: Checkout graalvm/mx - uses: actions/checkout@v3 - with: - repository: graalvm/mx.git - fetch-depth: 1 - ref: master - path: ${{ env.MX_PATH }} - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.8' - - name: Get latest Quarkus release - run: | - export QUARKUS_VERSION=main #$(curl https://repo1.maven.org/maven2/io/quarkus/quarkus-bom/maven-metadata.xml | awk -F"[<>]" '/latest/ {print $3}') - echo Getting Quarkus $QUARKUS_VERSION - curl --output quarkus.tgz -sL https://api.github.com/repos/quarkusio/quarkus/tarball/$QUARKUS_VERSION - mkdir ${QUARKUS_PATH} - tar xf quarkus.tgz -C ${QUARKUS_PATH} --strip-components=1 - - uses: actions/cache@v3 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - uses: actions/cache@v3 - with: - path: ~/.mx - key: ${{ runner.os }}-mx-${{ hashFiles('**/suite.py') }} - restore-keys: | - ${{ runner.os }}-mx- - - name: Fetch LabsJDK - run: | - mkdir jdk-dl - ${MX_PATH}/mx --java-home= fetch-jdk --jdk-id labsjdk-ce-21 --to jdk-dl --alias ${LABSJDK_HOME} - - name: Build graalvm native-image - run: | - export JAVA_HOME=${LABSJDK_HOME} - cd substratevm - ${MX_PATH}/mx --native=native-image,lib:jvmcicompiler --components="Native Image,LibGraal" build - mv $(${MX_PATH}/mx --native=native-image,lib:jvmcicompiler --components="Native Image,LibGraal" graalvm-home) ${GRAALVM_HOME} - ${GRAALVM_HOME}/bin/native-image --version - - name: Tar GraalVM - shell: bash - run: tar -czvf graalvm.tgz -C $(dirname ${GRAALVM_HOME}) $(basename ${GRAALVM_HOME}) - - name: Persist GraalVM build - uses: actions/upload-artifact@v1 - with: - name: graalvm - path: graalvm.tgz - - name: Build Quarkus - run: | - cd ${QUARKUS_PATH} - eval ./mvnw -e -B -Dquickly - - name: Read json file with native-tests matrix - id: read - run: | - json=$(tr -d '\n' < ${QUARKUS_PATH}/.github/native-tests.json ) - echo $json - echo "matrix=${json}" >> $GITHUB_OUTPUT - - name: Tar Maven Repo - shell: bash - run: tar -czvf maven-repo.tgz -C ~ .m2/repository - - name: Persist Maven Repo - uses: actions/upload-artifact@v1 - with: - name: maven-repo - path: maven-repo.tgz - - native-tests: - name: Native Tests - ${{matrix.category}} - needs: build-quarkus-and-graalvm - runs-on: ubuntu-latest - # Ignore the following YAML Schema error - timeout-minutes: ${{matrix.timeout}} - strategy: - max-parallel: 8 - fail-fast: false - matrix: ${{ fromJson(needs.build-quarkus-and-graalvm.outputs.matrix) }} - steps: - - name: Download GraalVM build - if: startsWith(matrix.os-name, 'ubuntu') - uses: actions/download-artifact@v1 - with: - name: graalvm - path: . - - name: Extract GraalVM build - if: startsWith(matrix.os-name, 'ubuntu') - shell: bash - run: tar -xzvf graalvm.tgz -C $(dirname ${GRAALVM_HOME}) - - name: Get latest Quarkus release - if: startsWith(matrix.os-name, 'ubuntu') - run: | - export QUARKUS_VERSION=main #$(curl https://repo1.maven.org/maven2/io/quarkus/quarkus-bom/maven-metadata.xml | awk -F"[<>]" '/latest/ {print $3}') - echo Getting Quarkus $QUARKUS_VERSION - curl --output quarkus.tgz -sL https://api.github.com/repos/quarkusio/quarkus/tarball/$QUARKUS_VERSION - mkdir ${QUARKUS_PATH} - tar xf quarkus.tgz -C ${QUARKUS_PATH} --strip-components=1 - - name: Reclaim Disk Space - if: startsWith(matrix.os-name, 'ubuntu') - run: ${QUARKUS_PATH}/.github/ci-prerequisites.sh - - name: Download Maven Repo - if: startsWith(matrix.os-name, 'ubuntu') - uses: actions/download-artifact@v1 - with: - name: maven-repo - path: . - - name: Extract Maven Repo - if: startsWith(matrix.os-name, 'ubuntu') - shell: bash - run: tar -xzf maven-repo.tgz -C ~ - - uses: graalvm/setup-graalvm@v1 - with: - version: 'latest' - java-version: '17' - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Build with Maven - if: startsWith(matrix.os-name, 'ubuntu') - env: - TEST_MODULES: ${{matrix.test-modules}} - run: | - export GRAALVM_HOME=${{ github.workspace }}/graalvm - cd ${QUARKUS_PATH} - ${GRAALVM_HOME}/bin/native-image --version - ./mvnw $COMMON_MAVEN_ARGS -f integration-tests -pl "$TEST_MODULES" $NATIVE_TEST_MAVEN_ARGS - - name: Prepare failure archive (if maven failed) - if: failure() - shell: bash - run: find . -type d -name '*-reports' -o -wholename '*/build/reports/tests/functionalTest' | tar -czf test-reports.tgz -T - - - name: Upload failure Archive (if maven failed) - uses: actions/upload-artifact@v1 - if: failure() - with: - name: test-reports-native-${{matrix.category}} - path: 'test-reports.tgz' diff --git a/.gitignore b/.gitignore index bcb17c6846774..b1b19b6cdbbeb 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,4 @@ visualizer/IdealGraphVisualizer/nbplatform/ /.src-rev *.interp *.tokens +/vm/tests/gh_workflows/CDTInspectorTest/target/ diff --git a/vm/tests/gh_workflows/CDTInspectorTest/pom.xml b/vm/tests/gh_workflows/CDTInspectorTest/pom.xml new file mode 100644 index 0000000000000..03e8c651de570 --- /dev/null +++ b/vm/tests/gh_workflows/CDTInspectorTest/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + com.oracle.truffle.tools.chromeinspector.workflowtest + CDTInspectorTest + 1.0-SNAPSHOT + jar + + + org.seleniumhq.selenium + selenium-api + 4.15.0 + + + org.seleniumhq.selenium + selenium-chrome-driver + 4.15.0 + + + org.seleniumhq.selenium + selenium-support + 4.15.0 + + + io.github.bonigarcia + webdrivermanager + 5.6.2 + + + + UTF-8 + 17 + 17 + com.oracle.truffle.tools.chromeinspector.workflowtest.Test + + diff --git a/vm/tests/gh_workflows/CDTInspectorTest/scripts/StepTest.js b/vm/tests/gh_workflows/CDTInspectorTest/scripts/StepTest.js new file mode 100644 index 0000000000000..5dec3832333bf --- /dev/null +++ b/vm/tests/gh_workflows/CDTInspectorTest/scripts/StepTest.js @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +// Finds the count of numbers that are smaller than the provided one. + +var data = [2, 3, 10, 1, 5, 20, 8, 18, 12, 30]; + +function binarySearch(array, element) { + let i1 = 0; + let i2 = array.length - 1; + while (i1 < i2) { + let i = (i1 + i2) >> 1; + let diff = array[i] - element; + if (diff > 0) { + i2 = i; + } else if (diff < 0) { + i1 = i; + } else { + return i; + } + } + return i1; +} + +data = data.sort(function(a,b){return a - b}); + +var index12 = binarySearch(data, 12); +var index8 = binarySearch(data, 8); +print('Index of element 12 is: ' + index12); +print('Index of element 8 is: ' + index8); diff --git a/vm/tests/gh_workflows/CDTInspectorTest/scripts/StepTest.js.out b/vm/tests/gh_workflows/CDTInspectorTest/scripts/StepTest.js.out new file mode 100644 index 0000000000000..eadf544327fe8 --- /dev/null +++ b/vm/tests/gh_workflows/CDTInspectorTest/scripts/StepTest.js.out @@ -0,0 +1,85 @@ +Opened file: StepTest.js +Stack (1) +:program StepTest.js:8 +Scope (3) +Local +this : Object global{Object: function Object() { +global Global +Stack (1) +:program StepTest.js:27 +Scope (3) +Local +this : Object global{Object: function Object() { +global Global +Stack (1) +:program StepTest.js:29 +Scope (3) +Local +this : Object global{Object: function Object() { +global Global +Stack (2) +binarySearch StepTest.js:11 +:program StepTest.js:29 +Scope (5) +Local +this : Object global{Object: function Object() { +array : \(10\) \[1, 2, 3, 5, 8,.* +element : 12 +global Global +Stack (2) +binarySearch StepTest.js:12 +:program StepTest.js:29 +Scope (6) +Local +this : Object global{Object: function Object() { +array : \(10\) \[1, 2, 3, 5, 8,.* +element : 12 +i1 : 0 +global Global +Stack (2) +binarySearch StepTest.js:13 +:program StepTest.js:29 +Scope (7) +Local +this : Object global{Object: function Object() { +array : \(10\) \[1, 2, 3, 5, 8,.* +element : 12 +i1 : 0 +i2 : 9 +global Global +Stack (2) +binarySearch StepTest.js:16 +:program StepTest.js:29 +Scope (10) +Block +diff : -4 +i : 4 +Local +this : Object global{Object: function Object() { +array : \(10\) \[1, 2, 3, 5, 8,.* +element : 12 +i1 : 0 +i2 : 9 +global Global +Stack (2) +binarySearch StepTest.js:18 +:program StepTest.js:29 +Scope (10) +Block +diff : -4 +i : 4 +Local +this : Object global{Object: function Object() { +array : \(10\) \[1, 2, 3, 5, 8,.* +element : 12 +i1 : 0 +i2 : 9 +global Global +Stack (1) +:program StepTest.js:29 +Scope (3) +Local +this : Object global{Object: function Object() { +global Global +Index of element 12 is: 6 +Index of element 8 is: 4 diff --git a/vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/AbstractTest.java b/vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/AbstractTest.java new file mode 100644 index 0000000000000..73d006f6147a0 --- /dev/null +++ b/vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/AbstractTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.tools.chromeinspector.workflowtest; + +import java.time.Duration; +import java.util.Objects; + +public abstract class AbstractTest { + + protected static final Duration TIMEOUT = Duration.ofSeconds(5); + + protected static void assertTrue(boolean condition, String message) { + if (!condition) { + fail(message); + } + } + + protected static void assertFalse(boolean condition, String message) { + assertTrue(!condition, message); + } + + protected static void assertNotNull(Object object, String message) { + assertTrue(object != null, message); + } + + protected static void assertEquals(Object expected, Object actual, String message) { + Objects.requireNonNull(expected); + if (expected.equals(actual)) { + return; + } + String cmpMessage = "Expected: '" + expected + "' but was: '" + actual + "'"; + fail(message == null ? cmpMessage : message + " " + cmpMessage); + } + + protected static void fail(String message) { + throw (message != null) ? new AssertionError(message) : new AssertionError(); + } +} diff --git a/vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/CDTLib.java b/vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/CDTLib.java new file mode 100644 index 0000000000000..e49ed3d649083 --- /dev/null +++ b/vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/CDTLib.java @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.tools.chromeinspector.workflowtest; + +import java.time.Duration; +import java.util.List; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import java.util.function.Function; +import org.openqa.selenium.By; +import org.openqa.selenium.InvalidArgumentException; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.NoSuchElementException; +import org.openqa.selenium.NoSuchShadowRootException; +import org.openqa.selenium.SearchContext; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; + +/** + * Library for Selenium tests of CDT (Chrome DevTools). + */ +public final class CDTLib { + + private CDTLib() { + throw new UnsupportedOperationException(); + } + + private static T find(WebDriver driver, Duration timeout, Function creator) throws NoSuchElementException { + return new WebDriverWait(driver, timeout).until(creator); + } + + private static void selectMainTab(WebDriver driver, String tabName) throws NoSuchElementException { + WebElement mainTabbedPane = ExpectedConditions.presenceOfElementLocated(By.className("main-tabbed-pane")).apply(driver); + WebElement tab = mainTabbedPane.getShadowRoot().findElement(By.id(tabName)); + if (!tab.isSelected()) { + tab.click(); + } + } + + public static void waitDisconnected(WebDriver driver, Duration timeout) { + WebElement dimmedPane = new WebDriverWait(driver, timeout).until((d) -> ExpectedConditions.presenceOfElementLocated(By.className("dimmed-pane")).apply(d)); + // We have dimmed pane + } + + public static final class Sources { + + private final WebElement sourcesView; + + public Sources(WebDriver driver) throws NoSuchElementException { + sourcesView = ExpectedConditions.presenceOfElementLocated(By.id("sources-panel-sources-view")).apply(driver); + } + + /** + * Select the Sources tab. + */ + public static void select(WebDriver driver) { + selectMainTab(driver, "tab-sources"); + } + + public static Sources find(WebDriver driver, Duration timeout) throws NoSuchElementException { + return CDTLib.find(driver, timeout, Sources::new); + } + + public void iterateOpenFiles(Consumer fileConsumer) { + for (WebElement div : sourcesView.findElements(By.tagName("div"))) { + try { + SearchContext shadow = div.getShadowRoot(); + List fileTitles = shadow.findElements(By.className("tabbed-pane-header-tab-title")); + if (!fileTitles.isEmpty()) { + for (WebElement title : fileTitles) { + fileConsumer.accept(title.getText()); + } + break; + } + } catch (NoSuchShadowRootException ex) { + // This div does not contain shadow root, continue with the next one. + } catch (InvalidArgumentException ex) { + // Invalid locator, no sources are displayed, most likely. + break; + } + } + } + } + + public static final class EditorGutter { + + private final WebElement gutter; + + public EditorGutter(WebDriver driver) { + WebElement editor = ExpectedConditions.presenceOfElementLocated(By.tagName("devtools-text-editor")).apply(driver); + gutter = editor.getShadowRoot().findElement(By.cssSelector(".cm-gutter.cm-lineNumbers")); + } + + public void clickAt(int lineNumber) { + List lines = gutter.findElements(By.className("cm-gutterElement")); + lines.get(lineNumber).click(); + } + } + + public static final class Actions { + + private final WebDriver driver; + private final List toolbarButtons; + + public Actions(WebDriver driver) throws NoSuchElementException { + this.driver = driver; + WebElement toolbar = ExpectedConditions.presenceOfElementLocated(By.cssSelector("div.scripts-debug-toolbar")).apply(driver); + toolbarButtons = toolbar.getShadowRoot().findElements(By.cssSelector("button.toolbar-button")); + if (toolbarButtons.size() < Action.values().length) { + throw new NoSuchElementException("Insufficient numebr of toolbar buttons: " + toolbarButtons.size() + " expecting " + Action.values().length); + } + } + + public static Actions find(WebDriver driver, Duration timeout) throws NoSuchElementException { + return CDTLib.find(driver, timeout, Actions::new); + } + + public void click(Action a) { + toolbarButtons.get(a.ordinal()).click(); + } + + public boolean isEnabled(Action a) { + WebElement button = toolbarButtons.get(a.ordinal()); + return button.isEnabled() && !"true".equals(button.getAttribute("disabled")); + } + + public void waitTillEnabled(Action a, boolean enabled, Duration timeout) throws TimeoutException { + new WebDriverWait(driver, timeout).until((d) -> isEnabled(a) == enabled); + } + + public String getName(Action a) { + return toolbarButtons.get(a.ordinal()).getAttribute("aria-label"); + } + + public enum Action { + PAUSE_RESUME, + STEP_OVER, + STEP_INTO, + STEP_OUT, + STEP, + DEACTIVATE_BREAKPOINTS + } + } + + public static final class CallFrames { + + private static final String CALL_FRAME_ITEM_SELECTOR = "div.call-frame-item"; + + private final WebDriver driver; + private final SearchContext framesContext; + private String lastTopFrameText = ""; + + public CallFrames(WebDriver driver) throws NoSuchElementException { + this.driver = driver; + WebElement toolbarDrawer = ExpectedConditions.presenceOfElementLocated(By.cssSelector("div.scripts-debug-toolbar-drawer")).apply(driver); + JavascriptExecutor jsExec = (JavascriptExecutor) driver; + WebElement toolbarDrawerParent = (WebElement) jsExec.executeScript("return arguments[0].parentElement", toolbarDrawer); + + SearchContext fContext = null; + for (WebElement vbox : toolbarDrawerParent.findElements(By.className("vbox"))) { + try { + SearchContext shadow = vbox.getShadowRoot(); + List frameElements = shadow.findElements(By.cssSelector(CALL_FRAME_ITEM_SELECTOR)); + if (!frameElements.isEmpty()) { + fContext = shadow; + break; + } + } catch (NoSuchShadowRootException ex) { + // Some vbox elements do not have shadow roots, skip them. + } + } + if (fContext == null) { + throw new NoSuchElementException("Frame elements not found."); + } + framesContext = fContext; + } + + public static CallFrames find(WebDriver driver, Duration timeout) throws NoSuchElementException { + return CDTLib.find(driver, timeout, CallFrames::new); + } + + public int getStackLength() { + return framesContext.findElements(By.cssSelector(CALL_FRAME_ITEM_SELECTOR)).size(); + } + + public String getFrameText(int frameIndex) { + return framesContext.findElements(By.cssSelector(CALL_FRAME_ITEM_SELECTOR)).get(frameIndex).getText(); + } + + public String getNewTopFrameText(Duration timeout) { + return new WebDriverWait(driver, timeout).until((d) -> { + String newText = getFrameText(0); + if (!newText.equals(lastTopFrameText)) { + lastTopFrameText = newText; + return newText; + } else { + return null; + } + }); + } + } + + public static final class Scope { + + private final SearchContext scopeContext; + + public Scope(WebDriver driver) throws NoSuchElementException { + WebElement toolbarDrawer = ExpectedConditions.presenceOfElementLocated(By.cssSelector("div.scripts-debug-toolbar-drawer")).apply(driver); + WebElement widget = toolbarDrawer.findElement(By.xpath(".//following-sibling::div")); + SearchContext shadowRoot = widget.findElement(By.xpath(".//div[4]/div")).getShadowRoot(); + scopeContext = shadowRoot.findElement(By.cssSelector("div.expanded")).getShadowRoot(); + } + + public String[] getScope() { + List liElements = scopeContext.findElements(By.cssSelector("li")); + int n = liElements.size(); + String[] result = new String[n]; + for (int i = 0; i < n; i++) { + result[i] = liElements.get(i).getText(); + } + return result; + } + } +} diff --git a/vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/Launcher.java b/vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/Launcher.java new file mode 100644 index 0000000000000..1520fa821feac --- /dev/null +++ b/vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/Launcher.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.tools.chromeinspector.workflowtest; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + + +/** + * Launcher of Graal process. Returns an URL to start the CDT with. + */ +public final class Launcher { + + private Launcher() { + throw new UnsupportedOperationException(); + } + + public static String launch(String graalLauncher, String scriptFile, boolean suspend, Consumer output) throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder(graalLauncher, "--engine.WarnInterpreterOnly=false", "--inspect=0", "--inspect.Suspend=" + suspend, "--inspect.Path=InspectCDT", scriptFile); + Process p = pb.start(); + AtomicReference devtoolsURL = new AtomicReference<>(); + CountDownLatch launcherStartedLatch = new CountDownLatch(1); + BufferedReader errorReader = new BufferedReader(new InputStreamReader(p.getErrorStream())); + Thread errorReaderThread = new Thread(() -> { + boolean haveURL = false; + String line; + try { + while ((line = errorReader.readLine()) != null) { + if (!haveURL) { + int i = line.indexOf("devtools://"); + if (i > 0) { + String url = line.substring(i); + devtoolsURL.set(url); + launcherStartedLatch.countDown(); + haveURL = true; + } + } + System.err.println(line); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + launcherStartedLatch.countDown(); + }, "Launcher Error Reader"); + errorReaderThread.start(); + BufferedReader outputReader = new BufferedReader(new InputStreamReader(p.getInputStream())); + Thread outputReaderThread = new Thread(() -> { + String line; + try { + while ((line = outputReader.readLine()) != null) { + output.accept(line); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + }, "Launcher Output Reader"); + outputReaderThread.start(); + launcherStartedLatch.await(); + + String url = devtoolsURL.get(); + return url; + } +} diff --git a/vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/Test.java b/vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/Test.java new file mode 100644 index 0000000000000..0a50c1f0ab6cc --- /dev/null +++ b/vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/Test.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.tools.chromeinspector.workflowtest; + +import io.github.bonigarcia.wdm.WebDriverManager; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.List; +import java.util.function.Consumer; +import java.util.regex.Pattern; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; + +/** + * The test executor. + */ +public class Test { + + /** + * @param args launcher and script file. + */ + public static void main(String[] args) throws Exception { + String graalLauncher = args[0]; + String scriptFile = args[1]; + String chromeBin = args[2]; + String chromeVersion = args[3].substring(0, args[3].indexOf('.')); + + WebDriver driver = createDriver(chromeBin, chromeVersion); + List goldenLines = Files.readAllLines(Paths.get(scriptFile + ".out")); + OutputComparator outputComparator = new OutputComparator(goldenLines); + String url = Launcher.launch(graalLauncher, scriptFile, true, outputComparator); + driver.get(url); + TestCDTBasics.test(driver, outputComparator); + driver.close(); + driver.quit(); + } + + private static WebDriver createDriver(String chromeBin, String chromeVersion) { + ChromeOptions options = new ChromeOptions(); + options.setBinary(chromeBin); + options.setBrowserVersion(chromeVersion); + options.addArguments("--headless=new"); + System.err.println("Creating driver using options: "+options); + WebDriver driver; + try { + driver = WebDriverManager.chromedriver().browserVersion(chromeVersion).driverVersion(chromeVersion).capabilities(options).create(); + //driver = new ChromeDriver(options); + } catch (Throwable t) { + System.err.println("LAUNCH: ERROR: "+t); + t.printStackTrace(System.err); + throw t; + } + driver.manage().timeouts().implicitlyWait(Duration.ofMillis(1000)); + return driver; + } + + private static class OutputComparator implements Consumer { + + private final List goldenLines; + private int index = 0; + private boolean error = false; + + OutputComparator(List goldenLines) { + this.goldenLines = goldenLines; + } + + @Override + public void accept(String line) { + String goldenLine = goldenLines.get(index++); + line = line.trim(); + if (!goldenLine.equals(line)) { + if (!Pattern.matches(goldenLine, line)) { + System.err.println("Output mismatch at line " + index + ". Expected: '" + goldenLine + "', actual: '" + line + "'."); + error = true; + System.exit(1); + } + } + System.out.println("[OUT] " + line); + } + } +} diff --git a/vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/TestCDTBasics.java b/vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/TestCDTBasics.java new file mode 100644 index 0000000000000..8e66371252ff9 --- /dev/null +++ b/vm/tests/gh_workflows/CDTInspectorTest/src/main/java/com/oracle/truffle/tools/chromeinspector/workflowtest/TestCDTBasics.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.tools.chromeinspector.workflowtest; + +import com.oracle.truffle.tools.chromeinspector.workflowtest.CDTLib.Actions; +import com.oracle.truffle.tools.chromeinspector.workflowtest.CDTLib.Actions.Action; +import com.oracle.truffle.tools.chromeinspector.workflowtest.CDTLib.CallFrames; +import com.oracle.truffle.tools.chromeinspector.workflowtest.CDTLib.EditorGutter; +import com.oracle.truffle.tools.chromeinspector.workflowtest.CDTLib.Scope; +import com.oracle.truffle.tools.chromeinspector.workflowtest.CDTLib.Sources; + +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; + +import org.openqa.selenium.WebDriver; + +/** + * Test of GraalVM Inspector with CDT (Chrome DevTools) UI. + */ +public class TestCDTBasics extends AbstractTest { + + /** + * Perform the test with the {@code driver} and provide the {@code output}. + */ + public static void test(WebDriver driver, Consumer output) throws TimeoutException { + Sources sources = Sources.find(driver, TIMEOUT); + sources.iterateOpenFiles(file -> output.accept("Opened file: " + file)); + + Actions actions = Actions.find(driver, TIMEOUT); + assertTrue(actions.isEnabled(Action.STEP), "Step not enabled."); + + CallFrames callFrames = CallFrames.find(driver, TIMEOUT); + printStack(callFrames, 1, output); + + Scope scope = new Scope(driver); + printScope(scope, output); + + actions.click(Action.STEP_OVER); + actions.waitTillEnabled(Action.STEP, true, TIMEOUT); + printStack(callFrames, 1, output); + printScope(scope, output); + + actions.click(Action.STEP_OVER); + actions.waitTillEnabled(Action.STEP, true, TIMEOUT); + printStack(callFrames, 1, output); + printScope(scope, output); + + actions.click(Action.STEP_INTO); + actions.waitTillEnabled(Action.STEP, true, TIMEOUT); + printStack(callFrames, 2, output); + printScope(scope, output); + + actions.click(Action.STEP); + actions.waitTillEnabled(Action.STEP, true, TIMEOUT); + printStack(callFrames, 2, output); + printScope(scope, output); + + actions.click(Action.STEP); + actions.waitTillEnabled(Action.STEP, true, TIMEOUT); + printStack(callFrames, 2, output); + printScope(scope, output); + + EditorGutter gutter = new EditorGutter(driver); + gutter.clickAt(16); // Set breakpoint at line 16 + actions.click(Action.PAUSE_RESUME); + + actions.waitTillEnabled(Action.STEP, true, TIMEOUT); + printStack(callFrames, 2, output); + printScope(scope, output); + + actions.click(Action.STEP); + actions.waitTillEnabled(Action.STEP, true, TIMEOUT); + printStack(callFrames, 2, output); + printScope(scope, output); + + actions.click(Action.DEACTIVATE_BREAKPOINTS); + + actions.click(Action.STEP_OUT); + actions.waitTillEnabled(Action.STEP, true, TIMEOUT); + printStack(callFrames, 1, output); + printScope(scope, output); + + gutter.clickAt(16); // Remove breakpoint at line 16 + + actions.click(Action.PAUSE_RESUME); + + // The program should finish, pause/resume will be disabled. + //actions.waitTillEnabled(Action.PAUSE_RESUME, false, TIMEOUT); // The pause/resume action is kept enabled. + CDTLib.waitDisconnected(driver, TIMEOUT); + } + + private static void printStack(CallFrames callFrames, int assertLength, Consumer output) { + int length = callFrames.getStackLength(); + assertEquals(assertLength, length, "Stack depth"); + output.accept("Stack (" + length + ")"); + output.accept(callFrames.getNewTopFrameText(TIMEOUT).replace('\n', ' ')); + for (int i = 1; i < length; i++) { + output.accept(callFrames.getFrameText(i).replace('\n', ' ')); + } + } + + private static void printScope(Scope scope, Consumer output) { + String[] elements = scope.getScope(); + output.accept("Scope (" + elements.length + ")"); + for (String s : elements) { + String var = s.replace('\n', ' '); + if (var.length() > 50) { + var = var.substring(0, 50); + } + output.accept(var); + } + } +}